jumpTreeDialog.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. <template>
  2. <hc-new-dialog v-model="moveModal" is-table title="跨节点移动" widths="72rem" @close="closeModal">
  3. <hc-page-split class="m-4" :options="{ sizes: [50, 50] }">
  4. <template #left>
  5. <hc-card scrollbar>
  6. <div v-loading="cityLoading" class="checkbox-container">
  7. <el-checkbox
  8. v-model="checkAll"
  9. :indeterminate="isIndeterminate"
  10. @change="handleCheckAllChange"
  11. >
  12. <span class="font-800">全选</span>
  13. </el-checkbox>
  14. <el-checkbox-group
  15. v-model="checkedCities"
  16. class="checkbox-group"
  17. @change="handleCheckedCitiesChange"
  18. >
  19. <el-checkbox
  20. v-for="city in cities"
  21. :key="city.pkeyId"
  22. :label="city.fullName "
  23. :value="city.pkeyId "
  24. class="checkbox-item"
  25. >
  26. {{ city.fullName }}
  27. </el-checkbox>
  28. </el-checkbox-group>
  29. </div>
  30. </hc-card>
  31. </template>
  32. <hc-card class="tree-card">
  33. <template #search>
  34. <div class="flex-1">
  35. <el-input v-model="searchInput" placeholder="请输入" clearable />
  36. </div>
  37. <div class="ml-2">
  38. <el-button
  39. hc-btn
  40. type="primary"
  41. @click="searchClick"
  42. >
  43. 搜索
  44. </el-button>
  45. </div>
  46. </template>
  47. <!-- 添加懒加载树 -->
  48. <el-scrollbar class="tree-scrollbar mt-3" style="height: 100%;">
  49. <el-tree
  50. v-if="!isShowSearch"
  51. ref="treeRef"
  52. node-key="id"
  53. :props="treeProps"
  54. :load="treeLoadNode"
  55. lazy
  56. :show-checkbox="true"
  57. :check-strictly="true"
  58. :check-on-click-node="true"
  59. highlight-current
  60. @check="handleCheckChange"
  61. >
  62. <template #default="{ node, data }">
  63. <span class="custom-tree-node">
  64. <span>{{ node.label }}</span>
  65. </span>
  66. </template>
  67. </el-tree>
  68. <el-tree
  69. v-else
  70. v-loading="treeLoading"
  71. node-key="id"
  72. default-expand-all
  73. :props="treeProps"
  74. :data="treeData"
  75. :show-checkbox="true"
  76. :check-strictly="true"
  77. :check-on-click-node="true"
  78. highlight-current
  79. @check="handleCheckChange"
  80. >
  81. <template #default="{ node, data }">
  82. <span class="custom-tree-node">
  83. <span>{{ node.label }}</span>
  84. </span>
  85. </template>
  86. </el-tree>
  87. </el-scrollbar>
  88. </hc-card>
  89. </hc-page-split>
  90. <template #footer>
  91. <el-button :loading="moveLoading" @click="submitMove(1)">保存并退出</el-button>
  92. <el-button type="primary" :loading="moveLoading" @click="submitMove(2)">保存并继续</el-button>
  93. </template>
  94. </hc-new-dialog>
  95. </template>
  96. <script setup>
  97. import { nextTick, ref, watch } from 'vue'
  98. import { getArrValue, getObjValue } from 'js-fast-way'
  99. import queryApi from '~api/data-fill/query'
  100. // 接收父组件传入的属性
  101. const props = defineProps({
  102. contractId: {
  103. type: String,
  104. default: '',
  105. },
  106. classType: {
  107. type: String,
  108. default: '',
  109. },
  110. authBtnTabKey: {
  111. type: String,
  112. default: '',
  113. },
  114. primaryKeyId: {
  115. type: String,
  116. default: '',
  117. },
  118. })
  119. //事件
  120. const emit = defineEmits(['close', 'save'])
  121. const contractId = ref(props.contractId)
  122. const classType = ref(props.classType)
  123. const authBtnTabKey = ref(props.authBtnTabKey)
  124. const primaryKeyId = ref(props.primaryKeyId)
  125. //监听
  126. watch(() => [
  127. props.contractId,
  128. props.classType,
  129. props.authBtnTabKey,
  130. props.primaryKeyId,
  131. ], ([cid, clas, tab, pkid]) => {
  132. contractId.value = cid
  133. classType.value = clas
  134. authBtnTabKey.value = tab
  135. primaryKeyId.value = pkid
  136. getSameLevelsTreeData()
  137. })
  138. const moveModal = defineModel('modelValue', {
  139. default: false,
  140. })
  141. const closeModal = ()=>{
  142. moveModal.value = false
  143. checkAll.value = false
  144. checkedCities.value = []
  145. cities.value = []
  146. treeRef.value.setCheckedKeys([])
  147. emit('close')
  148. }
  149. const checkAll = ref(false)
  150. const isIndeterminate = ref(false)
  151. const checkedCities = ref([])
  152. const cities = ref([])
  153. const handleCheckAllChange = (val) => {
  154. checkedCities.value = val ? cities.value.map(city => city.pkeyId) : []
  155. isIndeterminate.value = false
  156. }
  157. const handleCheckedCitiesChange = (value) => {
  158. const checkedCount = value.length
  159. checkAll.value = checkedCount === cities.value.length
  160. isIndeterminate.value = checkedCount > 0 && checkedCount < cities.value.length
  161. }
  162. const searchInput = ref('')
  163. // 树相关
  164. const treeRef = ref(null)
  165. const treeData = ref([])
  166. const isShowSearch = ref(false)
  167. const currentNode = ref(null)
  168. // 树配置
  169. const treeProps = {
  170. label: 'title',
  171. children: 'children',
  172. isLeaf: 'notExsitChild',
  173. }
  174. // 加载节点数据
  175. const treeLoadNode = async (node, resolve) => {
  176. const { level, data: item } = node
  177. let contractIdRelation = '',
  178. parentId = '',
  179. primaryKeyId = ''
  180. if (level !== 0) {
  181. const nodeData = getObjValue(item)
  182. contractIdRelation = nodeData?.contractIdRelation || ''
  183. parentId = contractIdRelation ? nodeData?.primaryKeyId : nodeData?.id
  184. primaryKeyId = nodeData?.id || ''
  185. }
  186. //获取数据
  187. const { data } = await queryApi.queryWbsTreeData({
  188. contractId: contractId.value || '',
  189. contractIdRelation,
  190. primaryKeyId,
  191. parentId,
  192. // classifyType: authBtnTabKey.value,
  193. classifyType: classType.value,
  194. tableOwner: authBtnTabKey.value,
  195. dataTime:new Date(),
  196. })
  197. resolve(getArrValue(data))
  198. }
  199. // 节点点击事件
  200. const handleCheckChange = (data, checked) => {
  201. // 确保只能选中一个节点
  202. const checkedNodes = treeRef.value.getCheckedNodes()
  203. if (checkedNodes.length > 1) {
  204. // 取消之前选中的节点
  205. checkedNodes.forEach(node => {
  206. if (node.id !== data.id) {
  207. treeRef.value.setChecked(node, false)
  208. }
  209. })
  210. }
  211. currentNode.value = checked ? data : null
  212. console.log('当前选中节点:', currentNode.value)
  213. }
  214. // 搜索功能
  215. const searchClick = () => {
  216. if (!searchInput.value) {
  217. isShowSearch.value = false
  218. } else {
  219. isShowSearch.value = true
  220. getSearchTreeData()
  221. }
  222. // TODO: 实现搜索逻辑
  223. console.log('搜索关键词:', searchInput.value)
  224. }
  225. const treeLoading = ref(false)
  226. const getSearchTreeData = async () => {
  227. treeLoading.value = true
  228. const { error, code, data }
  229. = await queryApi.getTreeNodeByQueryValueAndContractId({
  230. contractId: contractId.value,
  231. queryValue: searchInput.value,
  232. tableOwner: authBtnTabKey.value,
  233. })
  234. //判断状态
  235. if (!error && code === 200) {
  236. treeData.value = getArrValue(data)
  237. treeLoading.value = false
  238. } else {
  239. treeLoading.value = false
  240. treeData.value = []
  241. }
  242. }
  243. const cityLoading = ref(false)
  244. const getSameLevelsTreeData = async () => {
  245. cityLoading.value = true
  246. const { error, code, data }
  247. = await queryApi.getSiblingWbsContract({
  248. pKeyId: primaryKeyId.value,
  249. })
  250. //判断状态
  251. if (!error && code === 200) {
  252. cities.value = getArrValue(data)
  253. cityLoading.value = false
  254. } else {
  255. cityLoading.value = false
  256. cities.value = []
  257. }
  258. }
  259. const moveLoading = ref(false)
  260. const submitMove = async (type)=>{
  261. moveLoading.value = true
  262. const { error, code, data, msg } = await queryApi.moveNode({
  263. leftPkeyIds: checkedCities.value,
  264. rightPkeyId:currentNode.value.pKeyId,
  265. })
  266. moveLoading.value = false
  267. if (!error && code === 200) {
  268. window.$message?.success(msg ?? '操作成功')
  269. }
  270. if (type === 1) {
  271. emit('save')
  272. } else {
  273. checkAll.value = false
  274. checkedCities.value = []
  275. cities.value = []
  276. treeRef.value.setCheckedKeys([])
  277. getSameLevelsTreeData()
  278. searchClick()
  279. }
  280. }
  281. </script>
  282. <style lang="scss" scoped>
  283. .checkbox-container {
  284. display: flex;
  285. flex-direction: column;
  286. gap: 10px;
  287. .checkbox-group {
  288. display: flex;
  289. flex-direction: column;
  290. gap: 10px;
  291. }
  292. .checkbox-item {
  293. height: 32px;
  294. display: flex;
  295. align-items: center;
  296. }
  297. }
  298. .custom-tree-node {
  299. display: flex;
  300. align-items: center;
  301. font-size: 14px;
  302. .el-icon {
  303. margin-right: 4px;
  304. }
  305. }
  306. :deep(.el-tree-node__content) {
  307. height: 32px;
  308. &:hover {
  309. background-color: var(--el-tree-node-hover-bg-color);
  310. }
  311. }
  312. :deep(.el-tree) {
  313. // 自定义复选框样式使其看起来像单选框
  314. .el-checkbox {
  315. .el-checkbox__inner {
  316. border-radius: 50%;
  317. &::after {
  318. transform: rotate(45deg) scaleY(1);
  319. }
  320. }
  321. }
  322. }
  323. // 添加树卡片样式
  324. .tree-card {
  325. height: 100%;
  326. display: flex;
  327. flex-direction: column;
  328. :deep(.hc-card-body) {
  329. flex: 1;
  330. height: 0;
  331. display: flex;
  332. flex-direction: column;
  333. padding: 0;
  334. }
  335. }
  336. // 修改滚动容器样式
  337. .tree-scrollbar {
  338. flex: 1;
  339. height: 0;
  340. margin-top: 12px;
  341. :deep(.el-scrollbar__view) {
  342. height: 100%;
  343. }
  344. }
  345. </style>