audit-matters-catalog-tree.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. <template>
  2. <a-card
  3. :class="[
  4. $style.treewrap,
  5. $style.ywlxtree,
  6. { [$style.collapse]: fold, [$style.single]: single },
  7. ]"
  8. >
  9. <!-- 选择部门 -->
  10. <a-select v-show="isSelectDep" v-model="depvalue" :options="depOptions" @change="changedep">
  11. </a-select>
  12. <a-spin :spinning="spinning" :class="$style.spin" />
  13. <a-empty v-if="empty" />
  14. <a-tree
  15. v-else
  16. :key="key"
  17. ref="auditMattersCatalogTree"
  18. v-model="checkedKeys"
  19. :block-node="false"
  20. :show-icon="true"
  21. :show-line="showLine"
  22. :check-strictly="true"
  23. :checkable="false"
  24. :draggable="draggable"
  25. :tree-data="treeData"
  26. :replace-fields="replaceFields"
  27. :selected-keys="defaultSelectedKeys"
  28. :expanded-keys="expandedKeys"
  29. :default-expanded-keys="defaultTreeExpandedKeys"
  30. :load-data="onLoadData"
  31. @select="treeSelect"
  32. @check="treeCheck"
  33. @expand="onExpand"
  34. @drop="onDrop"
  35. >
  36. </a-tree>
  37. </a-card>
  38. </template>
  39. <script>
  40. import { Message } from 'ant-design-vue'
  41. import { getUserInfo } from '@/common/store-mixin'
  42. import auditMattersService from './audit-matters-service'
  43. import components from './_import-components/audit-matters-catalog-tree-import'
  44. export default {
  45. name: 'AuditMattersCatalogTree',
  46. metaInfo: {
  47. title: 'auditMattersCatalogTree',
  48. },
  49. components,
  50. props: {
  51. // 默认选中节点
  52. selectedKeys: {
  53. type: Array,
  54. default: function() {
  55. return []
  56. },
  57. },
  58. // 查看模式 manager维护权限 read查看权限
  59. managerType: {
  60. type: String,
  61. default: 'manager',
  62. },
  63. // 是否显示复选框
  64. checkable: {
  65. type: Boolean,
  66. default: true,
  67. },
  68. // 是否可以拖拽
  69. draggable: {
  70. type: Boolean,
  71. default: false,
  72. },
  73. // 是否显示连线
  74. showLine: {
  75. type: Boolean,
  76. default: false,
  77. },
  78. // 根节点名称
  79. topNodeText: {
  80. type: String,
  81. default: '根节点',
  82. },
  83. // 地址树接口数据源
  84. treeparams: {
  85. type: Object,
  86. default: () => {
  87. return {}
  88. },
  89. },
  90. // 默认展开节点id
  91. defaultExpandedKeys: {
  92. type: Array,
  93. default: () => {
  94. return ['0']
  95. },
  96. },
  97. // 是否显示隐藏按钮
  98. showPrimary: {
  99. type: Boolean,
  100. default: true,
  101. },
  102. // 地址树是否单选
  103. single: {
  104. type: Boolean,
  105. default: false,
  106. },
  107. refreshKey: {
  108. type: Number,
  109. default: 0,
  110. },
  111. /**
  112. * 是否展示部门下拉列表
  113. */
  114. isSelectDep: {
  115. type: Boolean,
  116. default: false,
  117. },
  118. },
  119. data() {
  120. return {
  121. treeData: [],
  122. defaultTreeExpandedKeys: ['0'],
  123. defaultSelectedKeys: ['0'],
  124. checkedKeys: [], // 选中的节点数据
  125. dataList: [], // 数组dataList,搜索要用
  126. spinning: true,
  127. empty: false,
  128. icontype: 'left',
  129. fold: false,
  130. replaceFields: {
  131. title: 'text',
  132. key: 'id',
  133. },
  134. expandedKeys: ['0'],
  135. backupsExpandedKeys: [],
  136. autoExpandParent: false,
  137. defaultTopNodeText: '审计事项分类',
  138. defaultTopNodeId: '0',
  139. key: 0,
  140. newtree: [],
  141. depOptions: [], // 部门下拉框列表值
  142. edit: true, // 当前节点是否可编辑
  143. depvalue: [], // 选择的组织数据
  144. }
  145. },
  146. created() {
  147. // 如果有默认展开节点,则赋值
  148. if (this.defaultExpandedKeys) {
  149. this.defaultTreeExpandedKeys = [...this.defaultExpandedKeys]
  150. }
  151. // 如果有传根节点名称,则赋值
  152. if (this.topNodeText) {
  153. this.defaultTopNodeText = this.topNodeText
  154. }
  155. // 如果有传默认选中节点,则赋值
  156. if (this.selectedKeys.length > 0) {
  157. this.defaultSelectedKeys = [...this.selectedKeys]
  158. }
  159. // this.initTreeData()
  160. },
  161. methods: {
  162. // 刷新树节点
  163. refreshNode(nodeId) {
  164. this.key++
  165. this.parentId = nodeId
  166. this.refreshData(this.treeData[0])
  167. },
  168. // 刷新树节点
  169. refreshData(treeData) {
  170. if (treeData.id + '' === this.parentId + '') {
  171. return auditMattersService
  172. .findIamAuditMattersCategoryTree(treeData.id, this.depvalue)
  173. .then((res) => {
  174. treeData.children = res.data
  175. if (res.data.length > 0) {
  176. treeData.isLeaf = false
  177. if (this.expandedKeys.indexOf(treeData.id) === -1) {
  178. this.expandedKeys = new Set(this.expandedKeys.push(treeData.id))
  179. }
  180. } else {
  181. treeData.isLeaf = true
  182. }
  183. return false
  184. })
  185. }
  186. if (treeData.children) {
  187. treeData.children.forEach((item) => {
  188. this.refreshData(item)
  189. })
  190. }
  191. },
  192. // 初始化部门下拉 分级授权获取部门下拉列表 维护权限
  193. initDeptList(formId) {
  194. auditMattersService.getManagedHierarchyOrg(formId).then((res) => {
  195. res.data.editNodes.forEach((item) => {
  196. this.depOptions.push({
  197. label: item.text,
  198. value: item.id,
  199. text: item.text,
  200. edit: true,
  201. })
  202. })
  203. if (this.managerType !== 'manager') {
  204. // 取编辑和查看节点并集
  205. res.data.viewNodes.forEach((item, index) => {
  206. const node = this.depOptions.find((nodeinfo) => {
  207. return nodeinfo.value === item.id
  208. })
  209. if (!node) {
  210. this.depOptions.push({
  211. label: item.text,
  212. value: item.id,
  213. text: item.text,
  214. edit: false,
  215. })
  216. }
  217. })
  218. }
  219. // 部门下拉框列表值赋值
  220. if (this.depOptions.length > 0) {
  221. this.depvalue = this.depOptions[0].value
  222. // 加载默认值
  223. this.defaultTopNodeId = this.depOptions[0].value
  224. this.defaultTopNodeText = this.depOptions[0].text + this.topNodeText
  225. this.defaultTreeExpandedKeys = [this.depOptions[0].value]
  226. this.defaultSelectedKeys = [this.depOptions[0].value]
  227. this.edit = this.depOptions[0].edit
  228. this.initTreeData(this.depOptions[0].value)
  229. this.$emit('treeSelect', [this.depOptions[0].value], {
  230. selectedNodes: [
  231. {
  232. data: {
  233. props: {
  234. id: this.depOptions[0].value,
  235. text: this.depOptions[0].text + this.topNodeText,
  236. edit: this.depOptions[0].edit,
  237. },
  238. },
  239. },
  240. ],
  241. })
  242. } else {
  243. this.defaultTopNodeId = null
  244. this.defaultTopNodeText = null
  245. this.defaultTreeExpandedKeys = []
  246. this.defaultSelectedKeys = []
  247. this.$emit('treeSelect', [9999999], {
  248. selectedNodes: [],
  249. })
  250. this.spinning = false
  251. this.empty = true
  252. }
  253. })
  254. },
  255. /***
  256. * 选中复选框时的事件
  257. */
  258. treeCheck(allKeys, echecked) {
  259. // echecked.checkedNodes 选中的所有节点信息为数组 .data.props 获取详细信息
  260. // echecked.node.dataRef 当前选中的节点信息
  261. if (this.single) {
  262. this.checkedKeys.checked = []
  263. this.checkedKeys.checked = [echecked.node.dataRef.id]
  264. }
  265. this.$emit('checkedKeys', echecked.node.dataRef, echecked.checkedNodes)
  266. },
  267. // 点击树
  268. treeSelect(selectedKeys, info) {
  269. this.defaultSelectedKeys = selectedKeys
  270. this.$emit('treeSelect', selectedKeys, info)
  271. },
  272. transformData(data) {
  273. return data.map((d) => {
  274. const { children, ...rest } = d
  275. rest.edit = this.edit
  276. return {
  277. ...rest,
  278. children: children && this.transformData(children),
  279. scopedSlots: { title: 'title' },
  280. }
  281. })
  282. },
  283. onExpand(expandedKeys) {
  284. // 用户点击展开时,取消自动展开效果
  285. this.expandedKeys = expandedKeys
  286. this.autoExpandParent = false
  287. },
  288. // 点击地址树展开时调用
  289. onLoadData(treeNode) {
  290. const parentId = parseInt(treeNode.dataRef.id)
  291. return auditMattersService
  292. .findIamAuditMattersCategoryTree(parentId, this.depvalue)
  293. .then((res) => {
  294. treeNode.dataRef.children = res.data
  295. // this.setDisabless(res.data)
  296. this.treeData = this.transformData([...this.treeData])
  297. })
  298. },
  299. // 添加是否可点击属性
  300. setDisabless(data) {
  301. return data.map((d) => {
  302. const props = d.props
  303. if (props && props.isEnd === '1') {
  304. // 置灰不可选
  305. d.disabled = true
  306. }
  307. })
  308. },
  309. // 初始化地址树
  310. initTreeData(depId) {
  311. const topDepId = depId === null ? 0 : depId
  312. auditMattersService.findIamAuditMattersCategoryTree(topDepId, topDepId).then((res) => {
  313. // if (res.data) {
  314. // this.defaultSelectedKeys = [res.data[0].id]
  315. // }
  316. // this.mmInitTreeData(res)
  317. this.spinning = false
  318. const treeNode = [
  319. {
  320. id: this.defaultTopNodeId,
  321. text: this.defaultTopNodeText,
  322. isLeaf: false,
  323. props: {
  324. isroot: true,
  325. },
  326. children: [],
  327. key: this.defaultTopNodeId,
  328. },
  329. ]
  330. if (res.data.length) {
  331. treeNode.children = res.data
  332. }
  333. this.treeData = this.transformData(treeNode)
  334. this.expandedKeys = this.defaultTreeExpandedKeys
  335. this.generateList(this.treeData)
  336. this.empty = false
  337. })
  338. },
  339. // 处理搜索用的dataList
  340. generateList(data) {
  341. for (let i = 0; i < data.length; i++) {
  342. const node = data[i]
  343. const key = node.id
  344. const title = node.text
  345. const props = node.props
  346. // 用来判断此几点是否已经配置了用户,需要后台返回标识位进行判断
  347. if (props.showuser) {
  348. data[i].scopedSlots.icon = 'hasuser'
  349. }
  350. this.dataList.push({ key, id: key, title: title, props })
  351. if (node.children) {
  352. this.generateList(node.children)
  353. }
  354. }
  355. },
  356. mmInitTreeData(res) {
  357. this.spinning = false
  358. if (res.data.length) {
  359. this.treeData = this.transformData(res.data)
  360. this.expandedKeys = this.defaultTreeExpandedKeys
  361. this.empty = false
  362. } else {
  363. this.empty = true
  364. }
  365. },
  366. // 刷新树方法
  367. refreshTree() {
  368. this.key++
  369. },
  370. // 组织下拉框change时触发
  371. changedep(value, option) {
  372. this.key++
  373. if (!value) {
  374. this.defaultTreeExpandedKeys = ['0']
  375. this.defaultSelectedKeys = ['0']
  376. this.defaultTopNodeId = '0'
  377. this.defaultTopNodeText = this.topNodeText
  378. } else {
  379. this.defaultTreeExpandedKeys = [value]
  380. this.defaultSelectedKeys = [value]
  381. this.defaultTopNodeId = value
  382. this.defaultTopNodeText = option.data.props.text + this.topNodeText
  383. this.edit = option.data.props.edit
  384. }
  385. this.initTreeData(value)
  386. this.$emit('depChanged', value, option)
  387. this.$emit('treeSelect', value, {
  388. selectedNodes: [
  389. {
  390. data: {
  391. props: {
  392. id: value,
  393. text: option.data.props.text + this.topNodeText,
  394. edit: option.data.props.edit,
  395. },
  396. },
  397. },
  398. ],
  399. })
  400. },
  401. // 拖拽节点方法
  402. onDrop(info) {
  403. // 被插入节点信息
  404. const dropKey = info.node.eventKey
  405. const dropNode = info.node.dataRef
  406. // 拖拽节点信息
  407. const dragKey = info.dragNode.eventKey
  408. const dragNode = info.dragNode.dataRef
  409. const dropPos = info.node.pos.split('-')
  410. // -1表示之前 0表示合并 1表示之后
  411. const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1])
  412. // 分类合并至事项内
  413. if (dropNode.props.isroot && (dropPosition === 1) | (dropPosition === -1)) {
  414. Message.info('不能将分类拖拽至与根节点同层级', 1)
  415. return false
  416. }
  417. const loop = (data, key, callback) => {
  418. data.forEach((item, index, arr) => {
  419. if (item.id === key) {
  420. return callback(item, index, arr)
  421. }
  422. if (item.children) {
  423. return loop(item.children, key, callback)
  424. }
  425. })
  426. }
  427. const data = this.treeData
  428. // Find dragObject
  429. let dragObj
  430. loop(data, dragKey, (item, index, arr) => {
  431. arr.splice(index, 1)
  432. dragObj = item
  433. })
  434. if (!info.dropToGap) {
  435. // Drop on the content
  436. loop(data, dropKey, (item) => {
  437. item.children = item.children || []
  438. // where to insert 示例添加到尾部,可以是随意位置
  439. item.children.push(dragObj)
  440. })
  441. } else if (
  442. (info.node.children || []).length > 0 && // Has children
  443. info.node.expanded && // Is expanded
  444. dropPosition === 1 // On the bottom gap
  445. ) {
  446. loop(data, dropKey, (item) => {
  447. item.children = item.children || []
  448. // where to insert 示例添加到尾部,可以是随意位置
  449. item.children.unshift(dragObj)
  450. })
  451. } else {
  452. let ar
  453. let i
  454. loop(data, dropKey, (item, index, arr) => {
  455. ar = arr
  456. i = index
  457. })
  458. if (dropPosition === -1) {
  459. ar.splice(i, 0, dragObj)
  460. } else {
  461. ar.splice(i + 1, 0, dragObj)
  462. }
  463. }
  464. this.treeData = data
  465. // 拖拽完成,调用接口
  466. const dragParams = {
  467. nodeId: dragNode.id,
  468. targetnodeType: dropNode.props.isroot ? 'root' : 'category', // category/root
  469. targetnodeId: dropNode.id,
  470. position: dropPosition, // -1 0 1 之前 合并 之后
  471. }
  472. auditMattersService.dragNode(dragParams).then((res) => {
  473. if (!res) {
  474. Message.info(dropNode.text + '节点下存在相同分类编号,拖拽失败')
  475. } else {
  476. this.$emit('dragNodeOk')
  477. }
  478. })
  479. },
  480. },
  481. }
  482. </script>
  483. <style module lang="scss">
  484. @use '@/common/design' as *;
  485. .spin {
  486. width: 100%;
  487. line-height: 30;
  488. }
  489. .ywlxtree {
  490. :global(.ant-tree-title) {
  491. display: inline-block;
  492. width: 100%;
  493. overflow: hidden;
  494. text-overflow: ellipsis;
  495. white-space: nowrap;
  496. }
  497. :global(.ant-input-search) {
  498. margin: 8px 0;
  499. overflow: hidden;
  500. }
  501. .active {
  502. color: $primary-color;
  503. }
  504. :global(.ant-select) {
  505. width: 100%;
  506. overflow: hidden;
  507. }
  508. }
  509. .treewrap {
  510. position: relative;
  511. display: flex;
  512. flex-direction: column;
  513. width: 20%;
  514. min-height: 100%;
  515. margin-right: $padding-lg;
  516. transition: width 0.2s;
  517. .fold {
  518. position: absolute;
  519. top: calc(50% - 30px);
  520. right: -15px;
  521. z-index: 2;
  522. width: 15px;
  523. height: 75px;
  524. padding: 0;
  525. border-radius: 0 10px 10px 0;
  526. }
  527. :global(.ant-tree) {
  528. overflow: hidden;
  529. text-overflow: ellipsis;
  530. white-space: nowrap;
  531. }
  532. :global(.ant-card-body) {
  533. background: $white;
  534. }
  535. }
  536. .collapse {
  537. width: 0;
  538. :global(.ant-card-body) {
  539. background: transparent;
  540. :global(.ant-empty) {
  541. display: none;
  542. }
  543. }
  544. }
  545. // 单选样式
  546. .single {
  547. :global .ant-tree-checkbox {
  548. .ant-tree-checkbox-inner {
  549. border-radius: 100px;
  550. &::after {
  551. position: absolute;
  552. top: 3px;
  553. left: 3px;
  554. display: table;
  555. width: 8px;
  556. height: 8px;
  557. content: ' ';
  558. background-color: $primary-color;
  559. border: 0;
  560. border-radius: 8px;
  561. opacity: 0;
  562. transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
  563. transform: scale(0);
  564. }
  565. }
  566. &.ant-tree-checkbox-checked {
  567. &::after {
  568. position: absolute;
  569. top: 16.67%;
  570. left: 0;
  571. width: 100%;
  572. height: 66.67%;
  573. content: '';
  574. border: 1px solid #1890ff;
  575. border-radius: 50%;
  576. animation: antRadioEffect 0.36s ease-in-out;
  577. animation-fill-mode: both;
  578. }
  579. .ant-tree-checkbox-inner {
  580. background-color: $white;
  581. &::after {
  582. opacity: 1;
  583. transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
  584. transform: scale(1);
  585. }
  586. }
  587. }
  588. }
  589. }
  590. </style>