|
@@ -1,6 +1,7 @@
|
|
|
<template>
|
|
|
<hc-new-dialog v-model="moveModal" is-table title="跨节点移动" widths="72rem" @close="closeModal">
|
|
|
<hc-page-split class="m-4" :options="{ sizes: [50, 50] }">
|
|
|
+ <!-- 左侧内容保持不变 -->
|
|
|
<template #left>
|
|
|
<hc-card scrollbar>
|
|
|
<div v-loading="cityLoading" class="checkbox-container">
|
|
@@ -29,6 +30,7 @@
|
|
|
</div>
|
|
|
</hc-card>
|
|
|
</template>
|
|
|
+
|
|
|
<hc-card class="tree-card">
|
|
|
<template #search>
|
|
|
<div class="flex-1">
|
|
@@ -44,30 +46,37 @@
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <!-- 添加懒加载树 -->
|
|
|
+
|
|
|
+ <!-- 修改树的渲染方式 -->
|
|
|
<el-scrollbar class="tree-scrollbar mt-3" style="height: 100%;">
|
|
|
+ <!-- 普通树 -->
|
|
|
<el-tree
|
|
|
v-if="!isShowSearch"
|
|
|
ref="treeRef"
|
|
|
- node-key="id"
|
|
|
-
|
|
|
+ :key="treeKey"
|
|
|
+ node-key="id"
|
|
|
:props="treeProps"
|
|
|
:load="treeLoadNode"
|
|
|
lazy
|
|
|
- :show-checkbox="true"
|
|
|
:check-strictly="true"
|
|
|
- :check-on-click-node="true"
|
|
|
-
|
|
|
highlight-current
|
|
|
- @check="handleCheckChange"
|
|
|
+ @node-click="handleNodeClick"
|
|
|
>
|
|
|
<template #default="{ node, data }">
|
|
|
<span class="custom-tree-node">
|
|
|
+ <!-- 使用单选框替代复选框 -->
|
|
|
+ <el-radio
|
|
|
+ v-model="selectedNodeId"
|
|
|
+ :value="data.id"
|
|
|
+ :disabled="data.nodeType === 6"
|
|
|
+ class="mr-2"
|
|
|
+ />
|
|
|
<span>{{ node.label }}</span>
|
|
|
-
|
|
|
</span>
|
|
|
</template>
|
|
|
</el-tree>
|
|
|
+
|
|
|
+ <!-- 搜索结果树 -->
|
|
|
<el-tree
|
|
|
v-else
|
|
|
v-loading="treeLoading"
|
|
@@ -75,18 +84,20 @@
|
|
|
default-expand-all
|
|
|
:props="treeProps"
|
|
|
:data="treeData"
|
|
|
-
|
|
|
- :show-checkbox="true"
|
|
|
:check-strictly="true"
|
|
|
- :check-on-click-node="true"
|
|
|
-
|
|
|
highlight-current
|
|
|
- @check="handleCheckChange"
|
|
|
+ @node-click="handleNodeClick"
|
|
|
>
|
|
|
<template #default="{ node, data }">
|
|
|
<span class="custom-tree-node">
|
|
|
+ <!-- 使用单选框替代复选框 -->
|
|
|
+ <el-radio
|
|
|
+ v-model="selectedNodeId"
|
|
|
+ :value="data.id"
|
|
|
+ :disabled="data.nodeType === 6"
|
|
|
+ class="mr-2"
|
|
|
+ />
|
|
|
<span>{{ node.label }}</span>
|
|
|
-
|
|
|
</span>
|
|
|
</template>
|
|
|
</el-tree>
|
|
@@ -104,6 +115,7 @@
|
|
|
import { nextTick, ref, watch } from 'vue'
|
|
|
import { getArrValue, getObjValue } from 'js-fast-way'
|
|
|
import queryApi from '~api/data-fill/query'
|
|
|
+
|
|
|
// 接收父组件传入的属性
|
|
|
const props = defineProps({
|
|
|
contractId: {
|
|
@@ -123,15 +135,16 @@ const props = defineProps({
|
|
|
default: '',
|
|
|
},
|
|
|
})
|
|
|
-//事件
|
|
|
+
|
|
|
+// 事件
|
|
|
const emit = defineEmits(['close', 'save'])
|
|
|
const contractId = ref(props.contractId)
|
|
|
const classType = ref(props.classType)
|
|
|
const authBtnTabKey = ref(props.authBtnTabKey)
|
|
|
const primaryKeyId = ref(props.primaryKeyId)
|
|
|
-//监听
|
|
|
-watch(() => [
|
|
|
|
|
|
+// 监听
|
|
|
+watch(() => [
|
|
|
props.contractId,
|
|
|
props.classType,
|
|
|
props.authBtnTabKey,
|
|
@@ -141,21 +154,27 @@ watch(() => [
|
|
|
classType.value = clas
|
|
|
authBtnTabKey.value = tab
|
|
|
primaryKeyId.value = pkid
|
|
|
- getSameLevelsTreeData()
|
|
|
+
|
|
|
})
|
|
|
+
|
|
|
const moveModal = defineModel('modelValue', {
|
|
|
default: false,
|
|
|
})
|
|
|
+watch(() => moveModal.value, (val) => {
|
|
|
+ if (val && primaryKeyId.value) {
|
|
|
+ getSameLevelsTreeData()
|
|
|
+ }
|
|
|
+})
|
|
|
const closeModal = ()=>{
|
|
|
moveModal.value = false
|
|
|
- checkAll.value = false
|
|
|
-
|
|
|
- checkedCities.value = []
|
|
|
- cities.value = []
|
|
|
- treeRef.value.setCheckedKeys([])
|
|
|
+ checkAll.value = false
|
|
|
+ checkedCities.value = []
|
|
|
+ cities.value = []
|
|
|
+ selectedNodeId.value = null // 重置选中状态
|
|
|
emit('close')
|
|
|
}
|
|
|
|
|
|
+// 左侧复选框相关
|
|
|
const checkAll = ref(false)
|
|
|
const isIndeterminate = ref(false)
|
|
|
const checkedCities = ref([])
|
|
@@ -165,34 +184,32 @@ const handleCheckAllChange = (val) => {
|
|
|
checkedCities.value = val ? cities.value.map(city => city.pkeyId) : []
|
|
|
isIndeterminate.value = false
|
|
|
}
|
|
|
+
|
|
|
const handleCheckedCitiesChange = (value) => {
|
|
|
const checkedCount = value.length
|
|
|
checkAll.value = checkedCount === cities.value.length
|
|
|
isIndeterminate.value = checkedCount > 0 && checkedCount < cities.value.length
|
|
|
}
|
|
|
+
|
|
|
+// 搜索相关
|
|
|
const searchInput = ref('')
|
|
|
|
|
|
-// 树相关
|
|
|
+// 树相关 - 修改为单选
|
|
|
const treeRef = ref(null)
|
|
|
const treeData = ref([])
|
|
|
const isShowSearch = ref(false)
|
|
|
-const currentNode = ref(null)
|
|
|
+const selectedNodeId = ref(null) // 存储当前选中的节点ID
|
|
|
+const currentNode = ref(null) // 存储当前选中的节点数据
|
|
|
|
|
|
-// 树配置
|
|
|
+// 树配置 - 移除disabled配置,在单选框中处理
|
|
|
const treeProps = {
|
|
|
label: 'title',
|
|
|
children: 'children',
|
|
|
isLeaf: 'notExsitChild',
|
|
|
- disabled: (data) => {
|
|
|
- // 当节点类型为6时不能能选择
|
|
|
- return data.nodeType === 6
|
|
|
- },
|
|
|
}
|
|
|
-
|
|
|
+const treeKey = ref(0) // 用于强制刷新树组件
|
|
|
// 加载节点数据
|
|
|
-
|
|
|
const treeLoadNode = async (node, resolve) => {
|
|
|
-
|
|
|
const { level, data: item } = node
|
|
|
|
|
|
let contractIdRelation = '',
|
|
@@ -204,112 +221,127 @@ const treeLoadNode = async (node, resolve) => {
|
|
|
parentId = contractIdRelation ? nodeData?.primaryKeyId : nodeData?.id
|
|
|
primaryKeyId = nodeData?.id || ''
|
|
|
}
|
|
|
- //获取数据
|
|
|
+
|
|
|
+ // 获取数据
|
|
|
const { data } = await queryApi.queryWbsTreeData({
|
|
|
contractId: contractId.value || '',
|
|
|
contractIdRelation,
|
|
|
primaryKeyId,
|
|
|
parentId,
|
|
|
- // classifyType: authBtnTabKey.value,
|
|
|
classifyType: classType.value,
|
|
|
tableOwner: authBtnTabKey.value,
|
|
|
- dataTime:new Date(),
|
|
|
+ dataTime: new Date(),
|
|
|
})
|
|
|
|
|
|
-
|
|
|
resolve(getArrValue(data))
|
|
|
}
|
|
|
-// 节点点击事件
|
|
|
-const handleCheckChange = (data, checked) => {
|
|
|
- // 确保只能选中一个节点
|
|
|
- const checkedNodes = treeRef.value.getCheckedNodes()
|
|
|
- if (checkedNodes.length > 1) {
|
|
|
- // 取消之前选中的节点
|
|
|
- checkedNodes.forEach(node => {
|
|
|
- if (node.id !== data.id) {
|
|
|
- treeRef.value.setChecked(node, false)
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- currentNode.value = checked ? data : null
|
|
|
-
|
|
|
+
|
|
|
+// 处理节点点击和单选逻辑
|
|
|
+const handleNodeClick = (data) => {
|
|
|
+ // 如果节点被禁用则不处理
|
|
|
+ if (data.nodeType === 6) return
|
|
|
+
|
|
|
+ // 设置选中状态
|
|
|
+ selectedNodeId.value = data.id
|
|
|
+ currentNode.value = data
|
|
|
}
|
|
|
|
|
|
+// 搜索功能
|
|
|
+
|
|
|
// 搜索功能
|
|
|
const searchClick = () => {
|
|
|
if (!searchInput.value) {
|
|
|
isShowSearch.value = false
|
|
|
-
|
|
|
+ // 重新加载原始树
|
|
|
+ if (treeRef.value) {
|
|
|
+ refreshTree()
|
|
|
+ }
|
|
|
} else {
|
|
|
isShowSearch.value = true
|
|
|
getSearchTreeData()
|
|
|
}
|
|
|
- // TODO: 实现搜索逻辑
|
|
|
- console.log('搜索关键词:', searchInput.value)
|
|
|
}
|
|
|
-const treeLoading = ref(false)
|
|
|
+// 刷新树的方法 - 替代reload
|
|
|
+const refreshTree = () => {
|
|
|
+ // 通过修改key值强制树组件重新渲染
|
|
|
+ treeKey.value += 1
|
|
|
+ // 重置选中状态
|
|
|
+ selectedNodeId.value = null
|
|
|
+ currentNode.value = null
|
|
|
+}
|
|
|
|
|
|
+
|
|
|
+const treeLoading = ref(false)
|
|
|
const getSearchTreeData = async () => {
|
|
|
treeLoading.value = true
|
|
|
- const { error, code, data }
|
|
|
- = await queryApi.getTreeNodeByQueryValueAndContractId({
|
|
|
- contractId: contractId.value,
|
|
|
- queryValue: searchInput.value,
|
|
|
- tableOwner: authBtnTabKey.value,
|
|
|
- })
|
|
|
- //判断状态
|
|
|
+ const { error, code, data } = await queryApi.getTreeNodeByQueryValueAndContractId({
|
|
|
+ contractId: contractId.value,
|
|
|
+ queryValue: searchInput.value,
|
|
|
+ tableOwner: authBtnTabKey.value,
|
|
|
+ })
|
|
|
+
|
|
|
if (!error && code === 200) {
|
|
|
-
|
|
|
treeData.value = getArrValue(data)
|
|
|
- treeLoading.value = false
|
|
|
} else {
|
|
|
- treeLoading.value = false
|
|
|
-
|
|
|
treeData.value = []
|
|
|
}
|
|
|
+ treeLoading.value = false
|
|
|
}
|
|
|
+
|
|
|
+// 获取左侧数据
|
|
|
const cityLoading = ref(false)
|
|
|
const getSameLevelsTreeData = async () => {
|
|
|
- cityLoading.value = true
|
|
|
- const { error, code, data }
|
|
|
- = await queryApi.getSiblingWbsContract({
|
|
|
- pKeyId: primaryKeyId.value,
|
|
|
-
|
|
|
- })
|
|
|
- //判断状态
|
|
|
+ cityLoading.value = true
|
|
|
+ const { error, code, data } = await queryApi.getSiblingWbsContract({
|
|
|
+ pKeyId: primaryKeyId.value,
|
|
|
+ })
|
|
|
+
|
|
|
if (!error && code === 200) {
|
|
|
-
|
|
|
cities.value = getArrValue(data)
|
|
|
- cityLoading.value = false
|
|
|
} else {
|
|
|
- cityLoading.value = false
|
|
|
-
|
|
|
cities.value = []
|
|
|
}
|
|
|
+ cityLoading.value = false
|
|
|
}
|
|
|
+
|
|
|
+// 提交移动
|
|
|
const moveLoading = ref(false)
|
|
|
const submitMove = async (type)=>{
|
|
|
- moveLoading.value = true
|
|
|
- const { error, code, data, msg } = await queryApi.moveNode({
|
|
|
- leftPkeyIds: checkedCities.value,
|
|
|
- rightPkeyId:currentNode.value.pKeyId,
|
|
|
- })
|
|
|
- moveLoading.value = false
|
|
|
- if (!error && code === 200) {
|
|
|
- window.$message?.success(msg ?? '操作成功')
|
|
|
- }
|
|
|
- if (type === 1) {
|
|
|
- emit('save')
|
|
|
- } else {
|
|
|
- checkAll.value = false
|
|
|
-
|
|
|
- checkedCities.value = []
|
|
|
- cities.value = []
|
|
|
- treeRef.value.setCheckedKeys([])
|
|
|
- getSameLevelsTreeData()
|
|
|
- searchClick()
|
|
|
+ // 验证是否选择了目标节点
|
|
|
+ if (!selectedNodeId.value) {
|
|
|
+ window.$message?.warning('请选择目标节点')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ moveLoading.value = true
|
|
|
+ const { error, code, data, msg } = await queryApi.moveNode({
|
|
|
+ leftPkeyIds: checkedCities.value,
|
|
|
+ rightPkeyId: currentNode.value.pKeyId,
|
|
|
+ })
|
|
|
+ moveLoading.value = false
|
|
|
+
|
|
|
+ if (!error && code === 200) {
|
|
|
+ window.$message?.success(msg ?? '操作成功')
|
|
|
+
|
|
|
+ if (type === 1) {
|
|
|
+ emit('save')
|
|
|
+ closeModal()
|
|
|
+ } else {
|
|
|
+ // 重置表单但保持弹窗打开
|
|
|
+ checkAll.value = false
|
|
|
+ checkedCities.value = []
|
|
|
+ selectedNodeId.value = null
|
|
|
+ currentNode.value = null
|
|
|
+ getSameLevelsTreeData()
|
|
|
+ // 刷新树数据
|
|
|
+ if (isShowSearch.value && searchInput.value) {
|
|
|
+ getSearchTreeData() // 搜索状态下重新搜索
|
|
|
+ } else {
|
|
|
+ refreshTree() // 普通状态下刷新树
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
</script>
|
|
|
|
|
@@ -332,8 +364,7 @@ const submitMove = async (type)=>{
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-// 添加树卡片样式
|
|
|
+// 树卡片样式
|
|
|
.tree-card {
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
@@ -348,7 +379,7 @@ const submitMove = async (type)=>{
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 修改滚动容器样式
|
|
|
+// 滚动容器样式
|
|
|
.tree-scrollbar {
|
|
|
flex: 1;
|
|
|
height: 0;
|
|
@@ -358,5 +389,14 @@ const submitMove = async (type)=>{
|
|
|
height: 100%;
|
|
|
}
|
|
|
}
|
|
|
-</style>
|
|
|
|
|
|
+// 调整单选框与文字的对齐方式
|
|
|
+:deep(.el-radio) {
|
|
|
+ vertical-align: middle;
|
|
|
+}
|
|
|
+
|
|
|
+.custom-tree-node {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+</style>
|