mtx-version-catalog-tree.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  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="mtxversionCatalogTree"
  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 MtxVersionService from './mtx-version-service'
  43. import components from './_import-components/mtx-version-catalog-tree-import'
  44. export default {
  45. name: 'MtxVersionCatalogTree',
  46. metaInfo: {
  47. title: 'MtxVersionCatalogTree',
  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 MtxVersionService.findIcIcmatrixCategoryTree(treeData.id, this.depvalue).then(
  172. (res) => {
  173. treeData.children = res.data
  174. if (res.data.length > 0) {
  175. treeData.isLeaf = false
  176. if (this.expandedKeys.indexOf(treeData.id) === -1) {
  177. this.expandedKeys = new Set(this.expandedKeys.push(treeData.id))
  178. }
  179. } else {
  180. treeData.isLeaf = true
  181. }
  182. return false
  183. }
  184. )
  185. }
  186. if (treeData.children) {
  187. treeData.children.forEach((item) => {
  188. this.refreshData(item)
  189. })
  190. }
  191. },
  192. // 初始化部门下拉 分级授权获取部门下拉列表 维护权限
  193. initDeptList(formId) {
  194. MtxVersionService.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 MtxVersionService.findIcIcmatrixCategoryTree(parentId, this.depvalue).then((res) => {
  292. treeNode.dataRef.children = res.data
  293. // this.setDisabless(res.data)
  294. this.treeData = this.transformData([...this.treeData])
  295. })
  296. },
  297. // 添加是否可点击属性
  298. setDisabless(data) {
  299. return data.map((d) => {
  300. const props = d.props
  301. if (props && props.isEnd === '1') {
  302. // 置灰不可选
  303. d.disabled = true
  304. }
  305. })
  306. },
  307. // 初始化地址树
  308. initTreeData(depId) {
  309. const topDepId = depId === null ? 0 : depId
  310. MtxVersionService.findIcIcmatrixCategoryTree(topDepId, topDepId).then((res) => {
  311. // if (res.data) {
  312. // this.defaultSelectedKeys = [res.data[0].id]
  313. // }
  314. // this.mmInitTreeData(res)
  315. this.spinning = false
  316. const treeNode = [
  317. {
  318. id: this.defaultTopNodeId,
  319. text: this.defaultTopNodeText,
  320. isLeaf: false,
  321. props: {
  322. isroot: true,
  323. },
  324. children: [],
  325. key: this.defaultTopNodeId,
  326. },
  327. ]
  328. if (res.data.length) {
  329. treeNode.children = res.data
  330. }
  331. this.treeData = this.transformData(treeNode)
  332. this.expandedKeys = this.defaultTreeExpandedKeys
  333. this.generateList(this.treeData)
  334. this.empty = false
  335. })
  336. },
  337. // 处理搜索用的dataList
  338. generateList(data) {
  339. for (let i = 0; i < data.length; i++) {
  340. const node = data[i]
  341. const key = node.id
  342. const title = node.text
  343. const props = node.props
  344. // 用来判断此几点是否已经配置了用户,需要后台返回标识位进行判断
  345. if (props.showuser) {
  346. data[i].scopedSlots.icon = 'hasuser'
  347. }
  348. this.dataList.push({ key, id: key, title: title, props })
  349. if (node.children) {
  350. this.generateList(node.children)
  351. }
  352. }
  353. },
  354. mmInitTreeData(res) {
  355. this.spinning = false
  356. if (res.data.length) {
  357. this.treeData = this.transformData(res.data)
  358. this.expandedKeys = this.defaultTreeExpandedKeys
  359. this.empty = false
  360. } else {
  361. this.empty = true
  362. }
  363. },
  364. // 刷新树方法
  365. refreshTree() {
  366. this.key++
  367. },
  368. // 组织下拉框change时触发
  369. changedep(value, option) {
  370. this.key++
  371. if (!value) {
  372. this.defaultTreeExpandedKeys = ['0']
  373. this.defaultSelectedKeys = ['0']
  374. this.defaultTopNodeId = '0'
  375. this.defaultTopNodeText = this.topNodeText
  376. } else {
  377. this.defaultTreeExpandedKeys = [value]
  378. this.defaultSelectedKeys = [value]
  379. this.defaultTopNodeId = value
  380. this.defaultTopNodeText = option.data.props.text + this.topNodeText
  381. this.edit = option.data.props.edit
  382. }
  383. this.initTreeData(value)
  384. this.$emit('depChanged', value, option)
  385. this.$emit('treeSelect', value, {
  386. selectedNodes: [
  387. {
  388. data: {
  389. props: {
  390. id: value,
  391. text: option.data.props.text + this.topNodeText,
  392. edit: option.data.props.edit,
  393. },
  394. },
  395. },
  396. ],
  397. })
  398. },
  399. // 拖拽节点方法
  400. onDrop(info) {
  401. // 被插入节点信息
  402. const dropKey = info.node.eventKey
  403. const dropNode = info.node.dataRef
  404. // 拖拽节点信息
  405. const dragKey = info.dragNode.eventKey
  406. const dragNode = info.dragNode.dataRef
  407. const dropPos = info.node.pos.split('-')
  408. // -1表示之前 0表示合并 1表示之后
  409. const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1])
  410. // 分类合并至事项内
  411. if (dropNode.props.isroot && (dropPosition === 1) | (dropPosition === -1)) {
  412. Message.info('不能将分类拖拽至与根节点同层级', 1)
  413. return false
  414. }
  415. const loop = (data, key, callback) => {
  416. data.forEach((item, index, arr) => {
  417. if (item.id === key) {
  418. return callback(item, index, arr)
  419. }
  420. if (item.children) {
  421. return loop(item.children, key, callback)
  422. }
  423. })
  424. }
  425. const data = this.treeData
  426. // Find dragObject
  427. let dragObj
  428. loop(data, dragKey, (item, index, arr) => {
  429. arr.splice(index, 1)
  430. dragObj = item
  431. })
  432. if (!info.dropToGap) {
  433. // Drop on the content
  434. loop(data, dropKey, (item) => {
  435. item.children = item.children || []
  436. // where to insert 示例添加到尾部,可以是随意位置
  437. item.children.push(dragObj)
  438. })
  439. } else if (
  440. (info.node.children || []).length > 0 && // Has children
  441. info.node.expanded && // Is expanded
  442. dropPosition === 1 // On the bottom gap
  443. ) {
  444. loop(data, dropKey, (item) => {
  445. item.children = item.children || []
  446. // where to insert 示例添加到尾部,可以是随意位置
  447. item.children.unshift(dragObj)
  448. })
  449. } else {
  450. let ar
  451. let i
  452. loop(data, dropKey, (item, index, arr) => {
  453. ar = arr
  454. i = index
  455. })
  456. if (dropPosition === -1) {
  457. ar.splice(i, 0, dragObj)
  458. } else {
  459. ar.splice(i + 1, 0, dragObj)
  460. }
  461. }
  462. this.treeData = data
  463. // 拖拽完成,调用接口
  464. const dragParams = {
  465. nodeId: dragNode.id,
  466. targetnodeType: dropNode.props.isroot ? 'root' : 'category', // category/root
  467. targetnodeId: dropNode.id,
  468. position: dropPosition, // -1 0 1 之前 合并 之后
  469. }
  470. MtxVersionService.dragNode(dragParams).then((res) => {
  471. if (!res) {
  472. Message.info(dropNode.text + '节点下存在相同分类编号,拖拽失败')
  473. }
  474. })
  475. },
  476. },
  477. }
  478. </script>
  479. <style module lang="scss">
  480. @use '@/common/design' as *;
  481. .spin {
  482. width: 100%;
  483. line-height: 30;
  484. }
  485. .ywlxtree {
  486. :global(.ant-tree-title) {
  487. display: inline-block;
  488. width: 100%;
  489. overflow: hidden;
  490. text-overflow: ellipsis;
  491. white-space: nowrap;
  492. }
  493. :global(.ant-input-search) {
  494. margin: 8px 0;
  495. overflow: hidden;
  496. }
  497. .active {
  498. color: $primary-color;
  499. }
  500. :global(.ant-select) {
  501. width: 100%;
  502. overflow: hidden;
  503. }
  504. }
  505. .treewrap {
  506. position: relative;
  507. display: flex;
  508. flex-direction: column;
  509. width: 20%;
  510. min-height: 100%;
  511. margin-right: $padding-lg;
  512. transition: width 0.2s;
  513. .fold {
  514. position: absolute;
  515. top: calc(50% - 30px);
  516. right: -15px;
  517. z-index: 2;
  518. width: 15px;
  519. height: 75px;
  520. padding: 0;
  521. border-radius: 0 10px 10px 0;
  522. }
  523. :global(.ant-tree) {
  524. overflow: hidden;
  525. text-overflow: ellipsis;
  526. white-space: nowrap;
  527. }
  528. :global(.ant-card-body) {
  529. background: $white;
  530. }
  531. }
  532. .collapse {
  533. width: 0;
  534. :global(.ant-card-body) {
  535. background: transparent;
  536. :global(.ant-empty) {
  537. display: none;
  538. }
  539. }
  540. }
  541. // 单选样式
  542. .single {
  543. :global .ant-tree-checkbox {
  544. .ant-tree-checkbox-inner {
  545. border-radius: 100px;
  546. &::after {
  547. position: absolute;
  548. top: 3px;
  549. left: 3px;
  550. display: table;
  551. width: 8px;
  552. height: 8px;
  553. content: ' ';
  554. background-color: $primary-color;
  555. border: 0;
  556. border-radius: 8px;
  557. opacity: 0;
  558. transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
  559. transform: scale(0);
  560. }
  561. }
  562. &.ant-tree-checkbox-checked {
  563. &::after {
  564. position: absolute;
  565. top: 16.67%;
  566. left: 0;
  567. width: 100%;
  568. height: 66.67%;
  569. content: '';
  570. border: 1px solid #1890ff;
  571. border-radius: 50%;
  572. animation: antRadioEffect 0.36s ease-in-out;
  573. animation-fill-mode: both;
  574. }
  575. .ant-tree-checkbox-inner {
  576. background-color: $white;
  577. &::after {
  578. opacity: 1;
  579. transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
  580. transform: scale(1);
  581. }
  582. }
  583. }
  584. }
  585. }
  586. </style>