|
|
@@ -24,6 +24,7 @@
|
|
|
:default-expanded-keys="defaultExpandedKeys"
|
|
|
:current-node-key="currentNodeKey"
|
|
|
:expand-on-click-node="expandOnClickNode"
|
|
|
+ :check-strictly="true"
|
|
|
@check="handleNodeCheck"
|
|
|
>
|
|
|
<!--
|
|
|
@@ -180,7 +181,12 @@ export default {
|
|
|
inject: ['openAiRobot'],//注入Ai机器人组件调用方法
|
|
|
// ==================== 数据状态 ====================
|
|
|
data() {
|
|
|
- return {};
|
|
|
+ return {
|
|
|
+ // 用户直接勾选的节点 key 列表(不含级联自动勾选的祖先/后代)
|
|
|
+ manuallyCheckedKeys: [],
|
|
|
+ // 防止程序化设置勾选状态时递归触发 @check 事件
|
|
|
+ _isUpdating: false,
|
|
|
+ };
|
|
|
},
|
|
|
computed:{
|
|
|
targetCommon()
|
|
|
@@ -210,10 +216,20 @@ export default {
|
|
|
treeData: {
|
|
|
handler(newVal) {
|
|
|
if (!Array.isArray(newVal) || newVal.length === 0) return;
|
|
|
- // 用 setTimeout 确保 el-tree 内部节点已完全渲染(比 $nextTick 更可靠)
|
|
|
+ // 用 setTimeout 确保 el-tree 内部节点已完全渲染
|
|
|
clearTimeout(this._defaultSelectTimer);
|
|
|
this._defaultSelectTimer = setTimeout(() => {
|
|
|
- this.applyDefaultFirstNode(newVal);
|
|
|
+ // 如果已有手动勾选的节点,在新数据上重新应用状态
|
|
|
+ if (this.manuallyCheckedKeys.length > 0 && this.showCheckbox) {
|
|
|
+ this._isUpdating = true;
|
|
|
+ this.reapplyCheckStates();
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this._isUpdating = false;
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 没有手动勾选节点时,执行默认选中第一个节点
|
|
|
+ this.applyDefaultFirstNode(newVal);
|
|
|
+ }
|
|
|
}, 100);
|
|
|
},
|
|
|
},
|
|
|
@@ -222,14 +238,113 @@ export default {
|
|
|
// ==================== 方法 ====================
|
|
|
methods: {
|
|
|
/**
|
|
|
- * 树节点勾选事件(多选,showCheckbox=true 时生效)
|
|
|
- * 将所有勾选节点的 key(即 nodeKey 字段值,如 knowledgeId)数组发给父组件
|
|
|
- * 勾选父节点时,el-tree 默认级联勾选其全部子节点,故数组中包含父节点及全部子节点 key
|
|
|
+ * 树节点勾选事件(自定义级联逻辑)
|
|
|
+ *
|
|
|
+ * 核心规则:
|
|
|
+ * - 同级节点可多选,只传递用户直接勾选的节点 knowledgeId
|
|
|
+ * - 勾选某节点时:其所有祖先和后代自动勾选并置灰(禁止操作),只传递当前节点
|
|
|
+ * - 取消勾选时:其所有祖先和后代恢复可操作状态
|
|
|
+ * - 已被置灰(因上下级被勾选)的节点禁止勾选/取消
|
|
|
+ *
|
|
|
* @param {Object} data - 当前勾选/取消的节点数据
|
|
|
- * @param {Object} checkedInfo - 勾选状态详情 { checkedNodes, checkedKeys, halfCheckedKeys, halfCheckedNodes }
|
|
|
+ * @param {Object} checkedInfo - 勾选状态详情
|
|
|
*/
|
|
|
handleNodeCheck(data, checkedInfo) {
|
|
|
- this.$emit("nodeCheck", checkedInfo.checkedKeys);
|
|
|
+ // 程序化设置状态时跳过,防止递归
|
|
|
+ if (this._isUpdating) return;
|
|
|
+
|
|
|
+ const treeRef = this.$refs.commonTree;
|
|
|
+ if (!treeRef) return;
|
|
|
+
|
|
|
+ const key = data[this.nodeKey];
|
|
|
+ const isChecked = checkedInfo.checkedKeys.includes(key);
|
|
|
+
|
|
|
+ if (isChecked) {
|
|
|
+ // 用户勾选了该节点 → 加入手动勾选列表
|
|
|
+ if (!this.manuallyCheckedKeys.includes(key)) {
|
|
|
+ this.manuallyCheckedKeys.push(key);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 用户取消勾选 → 从手动勾选列表移除
|
|
|
+ const index = this.manuallyCheckedKeys.indexOf(key);
|
|
|
+ if (index > -1) {
|
|
|
+ this.manuallyCheckedKeys.splice(index, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新应用所有勾选/置灰状态
|
|
|
+ this._isUpdating = true;
|
|
|
+ this.reapplyCheckStates();
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this._isUpdating = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 只传递用户直接勾选的节点 key
|
|
|
+ this.$emit("nodeCheck", [...this.manuallyCheckedKeys]);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重新应用所有勾选和置灰状态
|
|
|
+ * 策略:先全部清空,再根据 manuallyCheckedKeys 逐个应用
|
|
|
+ * 注意:node.disabled 是只读计算属性,需通过 this.$set(node.data, 'disabled', ...) 设置
|
|
|
+ */
|
|
|
+ reapplyCheckStates() {
|
|
|
+ const treeRef = this.$refs.commonTree;
|
|
|
+ if (!treeRef) return;
|
|
|
+
|
|
|
+ // 1. 清空所有节点的 checked 和 disabled 状态
|
|
|
+ this.clearAllCheckAndDisabled();
|
|
|
+
|
|
|
+ // 2. 遍历每个手动勾选的节点,设置其祖先和后代的勾选+置灰
|
|
|
+ this.manuallyCheckedKeys.forEach(key => {
|
|
|
+ const node = treeRef.getNode(key);
|
|
|
+ if (!node) return;
|
|
|
+
|
|
|
+ // 当前节点:勾选,不置灰(用户可取消勾选)
|
|
|
+ node.checked = true;
|
|
|
+ this.$set(node.data, 'disabled', false);
|
|
|
+
|
|
|
+ // 祖先节点:勾选 + 置灰,并展开(确保勾选节点可见)
|
|
|
+ let parent = node.parent;
|
|
|
+ while (parent && parent.level > 0) {
|
|
|
+ parent.checked = true;
|
|
|
+ this.$set(parent.data, 'disabled', true);
|
|
|
+ parent.expanded = true;
|
|
|
+ parent = parent.parent;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 后代节点:勾选 + 置灰
|
|
|
+ const checkDescendants = (n) => {
|
|
|
+ if (n.childNodes && n.childNodes.length > 0) {
|
|
|
+ n.childNodes.forEach(child => {
|
|
|
+ child.checked = true;
|
|
|
+ this.$set(child.data, 'disabled', true);
|
|
|
+ checkDescendants(child);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+ checkDescendants(node);
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清空整棵树的 checked 和 disabled 状态
|
|
|
+ * 注意:node.disabled 是只读计算属性,需通过 this.$set(node.data, 'disabled', false) 清除
|
|
|
+ */
|
|
|
+ clearAllCheckAndDisabled() {
|
|
|
+ const treeRef = this.$refs.commonTree;
|
|
|
+ if (!treeRef || !treeRef.store || !treeRef.store.root) return;
|
|
|
+
|
|
|
+ const clearNode = (node) => {
|
|
|
+ node.checked = false;
|
|
|
+ if (node.data) {
|
|
|
+ this.$set(node.data, 'disabled', false);
|
|
|
+ }
|
|
|
+ if (node.childNodes && node.childNodes.length > 0) {
|
|
|
+ node.childNodes.forEach(child => clearNode(child));
|
|
|
+ }
|
|
|
+ };
|
|
|
+ clearNode(treeRef.store.root);
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
@@ -260,22 +375,28 @@ export default {
|
|
|
|
|
|
/**
|
|
|
* 对外暴露方法:获取所有勾选节点的 key 数组
|
|
|
- * @param {Boolean} leafOnly
|
|
|
- * @returns {Array} 选中的节点 key 数组
|
|
|
+ * 新逻辑:只返回用户直接勾选的节点 key
|
|
|
+ * @param {Boolean} leafOnly - 保留兼容参数
|
|
|
+ * @returns {Array} 手动勾选的节点 key 数组
|
|
|
*/
|
|
|
getCheckedKeys(leafOnly = false) {
|
|
|
- if (!this.$refs.commonTree) return [];
|
|
|
- return this.$refs.commonTree.getCheckedKeys(leafOnly);
|
|
|
+ return [...this.manuallyCheckedKeys];
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 对外暴露方法:通过 keys 设置哪些节点被选中
|
|
|
+ * 新逻辑:keys 即手动勾选的节点列表
|
|
|
* @param {Array} keys - 要选中的节点 key 数组
|
|
|
- * @param {Boolean} leafOnly - 是否只设置叶子节点
|
|
|
+ * @param {Boolean} leafOnly - 是否只设置叶子节点(保留兼容,新逻辑中忽略)
|
|
|
*/
|
|
|
setCheckedKeys(keys, leafOnly = false) {
|
|
|
if (!this.$refs.commonTree) return;
|
|
|
- this.$refs.commonTree.setCheckedKeys(keys, leafOnly);
|
|
|
+ this.manuallyCheckedKeys = Array.isArray(keys) ? [...keys] : [];
|
|
|
+ this._isUpdating = true;
|
|
|
+ this.reapplyCheckStates();
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this._isUpdating = false;
|
|
|
+ });
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
@@ -283,7 +404,12 @@ export default {
|
|
|
*/
|
|
|
clearChecked() {
|
|
|
if (!this.$refs.commonTree) return;
|
|
|
- this.$refs.commonTree.setCheckedKeys([]);
|
|
|
+ this.manuallyCheckedKeys = [];
|
|
|
+ this._isUpdating = true;
|
|
|
+ this.clearAllCheckAndDisabled();
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this._isUpdating = false;
|
|
|
+ });
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
@@ -297,7 +423,7 @@ export default {
|
|
|
|
|
|
/**
|
|
|
* 默认勾选并展开第一个一级节点
|
|
|
- * 直接通过 el-tree 内部 store 获取节点实例,避免 node-key 与数据字段不匹配导致的 key 查找失败
|
|
|
+ * 新逻辑:只勾选第一个节点本身(手动勾选),其祖先和后代自动勾选+置灰
|
|
|
* @param {Array} data - 树形数据
|
|
|
*/
|
|
|
applyDefaultFirstNode(data) {
|
|
|
@@ -305,7 +431,7 @@ export default {
|
|
|
const treeRef = this.$refs.commonTree;
|
|
|
if (!treeRef || !treeRef.store || !treeRef.store.root) return;
|
|
|
|
|
|
- // 1. 获取第一个顶层节点实例(绕过 key 匹配)
|
|
|
+ // 1. 获取第一个顶层节点实例
|
|
|
const rootNode = treeRef.store.root;
|
|
|
const childNodes = rootNode.childNodes;
|
|
|
if (!childNodes || childNodes.length === 0) return;
|
|
|
@@ -313,9 +439,14 @@ export default {
|
|
|
const firstNodeInstance = childNodes[0];
|
|
|
if (!firstNodeInstance) return;
|
|
|
|
|
|
- // 2. 默认勾选第一个节点及其全部子节点(deep=true 级联勾选所有子节点)
|
|
|
+ // 2. 默认勾选第一个节点(手动勾选,只传该节点 key)
|
|
|
if (this.showCheckbox) {
|
|
|
- firstNodeInstance.setChecked(true, true);
|
|
|
+ this.manuallyCheckedKeys = [firstNodeInstance.key];
|
|
|
+ this._isUpdating = true;
|
|
|
+ this.reapplyCheckStates();
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this._isUpdating = false;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
// 3. 默认高亮选中第一个节点
|
|
|
@@ -332,11 +463,9 @@ export default {
|
|
|
};
|
|
|
expandAllChildren(firstNodeInstance);
|
|
|
|
|
|
- // 5. 触发 nodeCheck 事件,将所有勾选节点的 key 数组发给父组件
|
|
|
- // 仅发送一个数组参数(包含第一个节点及其所有子节点的 key)
|
|
|
+ // 5. 只传递手动勾选的节点 key(即第一个节点本身)
|
|
|
if (this.showCheckbox) {
|
|
|
- const checkedKeys = treeRef.getCheckedKeys();
|
|
|
- this.$emit("nodeCheck", checkedKeys);
|
|
|
+ this.$emit("nodeCheck", [...this.manuallyCheckedKeys]);
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
@@ -375,6 +504,8 @@ $btn_color: #4f3eff; // AI讲解按钮颜色 (用户要求)
|
|
|
height: 100%;
|
|
|
box-sizing: border-box;
|
|
|
padding: 8px 0;
|
|
|
+ overflow-x: auto;
|
|
|
+ overflow-y: auto;
|
|
|
|
|
|
/* ===================== 基础 ===================== */
|
|
|
.tree_container,
|