iam-item-tree.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. <template>
  2. <span>
  3. <a-spin ref="scroll" :spinning="false" :class="$style.spin">
  4. <a-empty v-if="empty" />
  5. <a-dropdown
  6. v-else
  7. v-model="showMenu"
  8. :get-popup-container="() => this.$refs.scroll.$el"
  9. :trigger="['contextmenu']"
  10. :disabled="single"
  11. @visibleChange="menuVisibleChange"
  12. >
  13. <div>
  14. <a-tree
  15. ref="auditMattersTree"
  16. v-model="checkedKeys"
  17. :show-icon="true"
  18. :show-line="true"
  19. :check-strictly="true"
  20. :expanded-keys="expandedKeys"
  21. :default-expanded-keys="defaultTreeExpandedKeys"
  22. :checkable="checkable"
  23. :tree-data="treeData"
  24. :load-data="onLoadData"
  25. @click.native="treeClickNative"
  26. @select="treeSelect"
  27. @check="treeCheck"
  28. @rightClick="treeRightClick"
  29. @contextmenu.native.capture="treeRightClickNative"
  30. @expand="onExpand"
  31. >
  32. <!-- 如果已配置负责人,则展示用户图标 -->
  33. <template slot="showMatters" slot-scope="item">
  34. <a-icon type="file-ppt" @click="showMatters(item)" />
  35. </template>
  36. </a-tree>
  37. </div>
  38. <a-menu slot="overlay" @click="menuClick">
  39. <a-menu-item key="1" :disabled="!selectedNodeHasSelectableChild">
  40. <a-icon type="check" />选中下一级节点
  41. </a-menu-item>
  42. <a-menu-item key="2" :disabled="!selectedNodeHasSelectableChild">
  43. <a-icon type="close" />取消下一级节点
  44. </a-menu-item>
  45. </a-menu>
  46. </a-dropdown>
  47. </a-spin>
  48. </span>
  49. </template>
  50. <script>
  51. import cloneDeep from 'lodash.clonedeep'
  52. import { getUserInfo } from '@/common/store-mixin'
  53. import icTreeService from '../ic-tree-service'
  54. import components from './_import-components/iam-item-tree-import'
  55. // 判断是否已经选择
  56. function isChecked(selectedKeys, eventKey) {
  57. return selectedKeys.indexOf(eventKey) === -1
  58. }
  59. export default {
  60. name: 'IamItemTree',
  61. metaInfo: {
  62. title: 'IamItemTree',
  63. },
  64. components,
  65. props: {
  66. /**
  67. * <p>获取数据的回调,获取根节点时,parentId为undefined</p>
  68. * <pre>(parentId,parent) => childrenItems</pre>
  69. */
  70. loadTreeData: {
  71. type: Function,
  72. required: true,
  73. },
  74. // 默认选中节点
  75. selectedKeys: {
  76. type: Array,
  77. default: function() {
  78. return []
  79. },
  80. },
  81. // 是否显示复选框
  82. checkable: {
  83. type: Boolean,
  84. default: true,
  85. },
  86. // 是否可以拖拽
  87. draggable: {
  88. type: Boolean,
  89. default: false,
  90. },
  91. // 是否显示连线
  92. showLine: {
  93. type: Boolean,
  94. default: false,
  95. },
  96. // 根节点名称
  97. topNodeText: {
  98. type: String,
  99. default: '根节点',
  100. },
  101. // 地址树接口数据源
  102. treeparams: {
  103. type: Object,
  104. default: () => {
  105. return {}
  106. },
  107. },
  108. // 默认展开节点id
  109. defaultExpandedKeys: {
  110. type: Array,
  111. default: undefined,
  112. },
  113. // 是否显示隐藏按钮
  114. showPrimary: {
  115. type: Boolean,
  116. default: true,
  117. },
  118. // 地址树是否单选
  119. single: {
  120. type: Boolean,
  121. default: false,
  122. },
  123. refreshKey: {
  124. type: Number,
  125. default: 0,
  126. },
  127. treeDataType: {
  128. type: String,
  129. default: '',
  130. },
  131. types: {
  132. type: String,
  133. default: 'NK',
  134. },
  135. versionId: {
  136. type: String,
  137. default: '',
  138. },
  139. orgId: {
  140. type: String,
  141. default: '',
  142. },
  143. orgName: {
  144. type: String,
  145. default: '',
  146. },
  147. selecttype: {
  148. type: String,
  149. default: 'control',
  150. },
  151. optionValue: {
  152. type: String,
  153. default: 'id',
  154. },
  155. /**
  156. * @ignore
  157. */
  158. optionLabel: {
  159. type: String,
  160. default: 'name',
  161. },
  162. isAbandonment: {
  163. type: String,
  164. default: 'NO',
  165. },
  166. },
  167. data() {
  168. return {
  169. defaultTopNodeId: -1,
  170. treeData: [],
  171. checkedKeys: {
  172. checked: [],
  173. halfChecked: [],
  174. }, // 选中的节点数据
  175. dataList: [], // 数组dataList,搜索要用
  176. spinning: true,
  177. empty: false,
  178. icontype: 'left',
  179. fold: false,
  180. // replaceFields: {
  181. // title: 'text',
  182. // key: 'code',
  183. // },
  184. openId: [], // 已经打开过的id
  185. defaultTreeExpandedKeys: ['-1'],
  186. expandedKeys: ['-1'],
  187. defaultTopNodeText: '根节点',
  188. key: 0,
  189. newtree: [],
  190. depOptions: [], // 部门下拉框列表值
  191. edit: true, // 当前节点是否可编辑
  192. depvalue: [], // 选择的组织数据
  193. selectedNode: undefined,
  194. showMenu: false,
  195. setAllChecking: false,
  196. }
  197. },
  198. computed: {
  199. selectedNodeHasSelectableChild() {
  200. return this.selectedNode?.dataRef.children?.some((item) => !item.disabled)
  201. },
  202. },
  203. created() {
  204. this.initTreeData()
  205. },
  206. methods: {
  207. treeRightClickNative(evt) {
  208. // 右键如果没有点击到树节点,不触发右键菜单
  209. if (this.single) return
  210. if (evt.target.nodeName !== 'SPAN') evt.stopPropagation()
  211. },
  212. treeClickNative() {
  213. // 点击树的其他区域,隐藏菜单
  214. if (this.single) return
  215. this.hideContextMemu()
  216. },
  217. menuClick(menu) {
  218. this.selectedNode.dataRef.children?.forEach((item) => {
  219. if (!item.disabled) this.itemSelect(item.id + '', menu.key === '1')
  220. })
  221. this.hideContextMemu()
  222. },
  223. itemSelect(key, checked) {
  224. if (checked) {
  225. if (!this.checkedKeys.checked.includes(key)) {
  226. this.checkedKeys.checked.push(key)
  227. }
  228. } else {
  229. this.checkedKeys.checked.splice(this.checkedKeys.checked.indexOf(key), 1)
  230. }
  231. },
  232. hideContextMemu() {
  233. this.showMenu = false
  234. this.menuVisibleChange(false)
  235. },
  236. menuVisibleChange(visible) {
  237. if (!visible) this.selectedNode = undefined
  238. },
  239. treeRightClick({ node }) {
  240. // 记录当前点击的节点
  241. if (this.single) return
  242. this.selectedNode = node
  243. },
  244. // 选中当前节点下的所有节点
  245. checkAllNode(nodeinfo, checked) {
  246. if (nodeinfo.children) {
  247. nodeinfo.children.forEach((node) => {
  248. if (checked) {
  249. if (isChecked(this.checkedKeys.checked, node.id + '')) {
  250. this.checkedKeys.checked.push(node.id + '')
  251. }
  252. } else {
  253. if (this.checkedKeys.checked.indexOf(node.id + '') !== -1) {
  254. this.checkedKeys.checked.splice(this.checkedKeys.checked.indexOf(node.id + ''), 1)
  255. }
  256. }
  257. // 下级节点也要处理
  258. if (node.children) {
  259. this.checkAllNode(node, checked)
  260. }
  261. })
  262. }
  263. },
  264. /***
  265. * 选中复选框时的事件
  266. */
  267. treeCheck(allKeys, echecked) {
  268. // echecked.checkedNodes 选中的所有节点信息为数组 .data.props 获取详细信息
  269. // echecked.node.dataRef 当前选中的节点信息
  270. const params = {
  271. auditOrgId: parseInt(this.orgId),
  272. versionId: parseInt(this.versionId),
  273. isAbandonment: this.isAbandonment,
  274. }
  275. if (this.single) {
  276. this.checkedKeys.checked = []
  277. this.checkedKeys.checked = [echecked.node.dataRef.id + '']
  278. }
  279. // 勾选复选框,则直接获取下级数据
  280. if (this.checkedKeys.checked.includes(echecked.node.dataRef.id + '')) {
  281. // 如果是事项,则不取下级
  282. // if (echecked.node.dataRef.props.isEnd === '1') return
  283. this.$emit('checking', true)
  284. if (this.selecttype === 'cross') {
  285. icTreeService
  286. .getCategoryTree(echecked.node.dataRef.id, params, this.types)
  287. .then((res) => {
  288. res.data.forEach((item) => {
  289. item.title = item.text
  290. item.code = item.id.toString()
  291. if (item.props.isEnd === '0') {
  292. item.isLeaf = false
  293. item.cat = true
  294. } else {
  295. item.cat = false
  296. this.isendids.push(item.id.toString())
  297. }
  298. })
  299. this.onCheckedLoadData(echecked.node, res.data)
  300. })
  301. } else {
  302. icTreeService.getMeasureTree(echecked.node.dataRef.id, params, this.types).then((res) => {
  303. res.data.forEach((item) => {
  304. item.title = item.text
  305. item.code = item.id.toString()
  306. if (item.props.disabled) {
  307. item.isLeaf = false
  308. item.cat = true
  309. } else {
  310. item.cat = false
  311. }
  312. })
  313. this.onCheckedLoadData(echecked.node, res.data)
  314. })
  315. }
  316. }
  317. // 判断当前节点是否选中,选中的话,就把下级节点也一并选中,否则都去掉
  318. if (allKeys.checked.includes(echecked.node.dataRef.id + '')) {
  319. this.checkAllNode(echecked.node.dataRef, true)
  320. } else {
  321. this.checkAllNode(echecked.node.dataRef, false)
  322. }
  323. this.$emit('checkedKeys', echecked.node.dataRef, echecked.checkedNodes)
  324. },
  325. // 点击树
  326. treeSelect(selectedKeys, info) {
  327. this.defaultSelectedKeys = selectedKeys
  328. if (this.single) {
  329. this.checkedKeys = []
  330. this.checkedKeys = [info.node.dataRef.id + '']
  331. } else {
  332. if (isChecked(this.checkedKeys.checked, info.node.dataRef.id + '')) {
  333. this.checkedKeys.checked.push(info.node.dataRef.id + '')
  334. } else {
  335. this.checkedKeys.checked.splice(
  336. this.checkedKeys.checked.indexOf(info.node.dataRef.id + ''),
  337. 1
  338. )
  339. }
  340. this.checkedKeys.checked = [...new Set(this.checkedKeys.checked)]
  341. }
  342. // 判断当前节点是否选中,选中的话,就把下级节点也一并选中,否则都去掉
  343. if (this.checkedKeys.checked.includes(info.node.dataRef.id + '')) {
  344. this.checkAllNode(info.node.dataRef, true)
  345. } else {
  346. this.checkAllNode(info.node.dataRef, false)
  347. }
  348. this.$emit('treeSelect', [...selectedKeys, ...this.checkedKeys.checked], info)
  349. },
  350. transformData(data) {
  351. return data.map((d) => {
  352. const { children, checkable, selectable, isLeaf, ...rest } = d
  353. return {
  354. ...rest,
  355. checkable: checkable ?? true,
  356. selectable: selectable ?? false,
  357. isLeaf: isLeaf ?? true,
  358. key: d[this.optionValue],
  359. children: children && this.transformData(children),
  360. originalValue: rest,
  361. code: rest.code,
  362. }
  363. })
  364. },
  365. // 点击地址树展开时调用
  366. onLoadData(treeNode) {
  367. const params = {
  368. auditOrgId: parseInt(this.orgId),
  369. versionId: parseInt(this.versionId),
  370. isAbandonment: this.isAbandonment,
  371. }
  372. if (this.selecttype === 'cross') {
  373. if (treeNode.dataRef.children !== null && treeNode.dataRef.children.length > 0) {
  374. console.log('有子节点,不请求')
  375. return new Promise((resolve) => {
  376. resolve()
  377. })
  378. } else {
  379. return icTreeService
  380. .getCategoryTree(treeNode.dataRef.id, params, this.types)
  381. .then((res) => {
  382. res.data.forEach((item) => {
  383. item.title = item.text
  384. item.code = item.id.toString()
  385. if (item.props.isEnd === '0') {
  386. item.isLeaf = false
  387. item.cat = true
  388. } else {
  389. item.cat = false
  390. this.isendids.push(item.id.toString())
  391. }
  392. })
  393. treeNode.dataRef.children = this.transformData(res.data)
  394. this.treeData = [...this.treeData]
  395. })
  396. }
  397. } else {
  398. if (treeNode.dataRef.children !== null && treeNode.dataRef.children.length > 0) {
  399. console.log('有子节点,不请求')
  400. return new Promise((resolve) => {
  401. resolve()
  402. })
  403. } else {
  404. return icTreeService
  405. .getMeasureTree(treeNode.dataRef.id, params, this.types)
  406. .then((res) => {
  407. res.data.forEach((item) => {
  408. item.title = item.text
  409. item.code = item.id.toString()
  410. if (item.props.disabled) {
  411. item.isLeaf = false
  412. item.cat = true
  413. } else {
  414. item.cat = false
  415. }
  416. })
  417. treeNode.dataRef.children = this.transformData(res.data)
  418. this.treeData = [...this.treeData]
  419. })
  420. }
  421. }
  422. },
  423. // 点击地址树前复选框时候调用
  424. onCheckedLoadData(treeNode, children) {
  425. const id = parseInt(treeNode.dataRef.id)
  426. if (!this.openId.includes(id)) {
  427. this.openId.push(id)
  428. }
  429. if (children !== null) {
  430. treeNode.dataRef.children = [...children]
  431. }
  432. // 如果根节点是选中状态,则将下级节点也选中
  433. if (this.checkedKeys.checked.includes(id + '')) {
  434. this.transformCheckData(children)
  435. setTimeout(() => {
  436. this.$emit('checking', false)
  437. }, 1000)
  438. }
  439. this.treeData = this.transformData([...this.treeData])
  440. },
  441. transformCheckData(data) {
  442. data = data || []
  443. data.forEach((d) => {
  444. const checkid = this.checkedKeys.checked
  445. if (!checkid.includes(d.id + '')) {
  446. this.checkedKeys.checked.push(d.id + '')
  447. }
  448. const { children } = d
  449. this.transformCheckData(children)
  450. })
  451. },
  452. initTreeData(depId) {
  453. const params = {
  454. auditOrgId: parseInt(this.orgId),
  455. versionId: parseInt(this.versionId),
  456. }
  457. this.defaultTopNodeText = this.orgName
  458. icTreeService.getCategoryTree(this.defaultTopNodeId, params, this.types).then((res1) => {
  459. this.spinning = false
  460. res1.data.forEach((item) => {
  461. item.title = item.text
  462. item.code = item.id.toString()
  463. item.key = item.id.toString()
  464. item.cat = true
  465. item.isLeaf = false
  466. })
  467. const treeNode = [
  468. {
  469. id: this.defaultTopNodeId,
  470. text: this.defaultTopNodeText,
  471. code: this.defaultTopNodeId.toString(),
  472. isLeaf: false,
  473. props: {
  474. isroot: true,
  475. },
  476. title: this.defaultTopNodeText,
  477. children: res1.data,
  478. cat: true,
  479. },
  480. ]
  481. if (this.selecttype === 'cross') {
  482. treeNode.forEach((i) => {
  483. if (i.children) {
  484. i.children.forEach((c) => {
  485. if (c.props.isEnd === '0') {
  486. c.isLeaf = false
  487. c.cat = true
  488. } else {
  489. c.cat = false
  490. this.isendids.push(c.id.toString())
  491. }
  492. })
  493. }
  494. })
  495. }
  496. this.treeData = this.transformData(treeNode)
  497. this.expandedKeys = this.defaultTreeExpandedKeys
  498. this.$emit('checking', false)
  499. this.dataLoaded = true
  500. })
  501. },
  502. // 刷新树方法
  503. refreshTree() {
  504. this.key++
  505. },
  506. onExpand(expandedKeys) {
  507. // 用户点击展开时,取消自动展开效果
  508. this.expandedKeys = expandedKeys
  509. this.autoExpandParent = false
  510. },
  511. },
  512. }
  513. </script>
  514. <style module lang="scss">
  515. @use '@/common/design' as *;
  516. .depselect {
  517. width: 200px;
  518. }
  519. .spin {
  520. width: 100%;
  521. line-height: 30;
  522. }
  523. .ywlxtree {
  524. :global(.ant-tree-title) {
  525. display: inline-block;
  526. width: 100%;
  527. overflow: hidden;
  528. text-overflow: ellipsis;
  529. white-space: nowrap;
  530. }
  531. :global(.ant-input-search) {
  532. margin: 8px 0;
  533. overflow: hidden;
  534. }
  535. .active {
  536. color: $primary-color;
  537. }
  538. :global(.ant-select) {
  539. width: 100%;
  540. overflow: hidden;
  541. }
  542. }
  543. .treewrap {
  544. position: relative;
  545. display: flex;
  546. flex-direction: column;
  547. width: 20%;
  548. min-height: 100%;
  549. margin-right: $padding-lg;
  550. transition: width 0.2s;
  551. .fold {
  552. position: absolute;
  553. top: calc(50% - 30px);
  554. right: -15px;
  555. z-index: 2;
  556. width: 15px;
  557. height: 75px;
  558. padding: 0;
  559. border-radius: 0 10px 10px 0;
  560. }
  561. :global(.ant-tree) {
  562. overflow: hidden;
  563. text-overflow: ellipsis;
  564. white-space: nowrap;
  565. }
  566. :global(.ant-card-body) {
  567. background: $white;
  568. }
  569. }
  570. .collapse {
  571. width: 0;
  572. :global(.ant-card-body) {
  573. background: transparent;
  574. :global(.ant-empty) {
  575. display: none;
  576. }
  577. }
  578. }
  579. // 单选样式
  580. .single {
  581. :global .ant-tree-checkbox {
  582. .ant-tree-checkbox-inner {
  583. border-radius: 100px;
  584. &::after {
  585. position: absolute;
  586. top: 3px;
  587. left: 3px;
  588. display: table;
  589. width: 8px;
  590. height: 8px;
  591. content: ' ';
  592. background-color: $primary-color;
  593. border: 0;
  594. border-radius: 8px;
  595. opacity: 0;
  596. transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
  597. transform: scale(0);
  598. }
  599. }
  600. &.ant-tree-checkbox-checked {
  601. &::after {
  602. position: absolute;
  603. top: 16.67%;
  604. left: 0;
  605. width: 100%;
  606. height: 66.67%;
  607. content: '';
  608. border: 1px solid #1890ff;
  609. border-radius: 50%;
  610. animation: antRadioEffect 0.36s ease-in-out;
  611. animation-fill-mode: both;
  612. }
  613. .ant-tree-checkbox-inner {
  614. background-color: $white;
  615. &::after {
  616. opacity: 1;
  617. transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
  618. transform: scale(1);
  619. }
  620. }
  621. }
  622. }
  623. }
  624. </style>