From 0bfaa88144bce22c1c9af034d47904b66303ab6a Mon Sep 17 00:00:00 2001 From: hanyuqing <1106611654@qq.com> Date: Wed, 7 Jan 2026 09:53:38 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=AF=BC=E5=87=BA(=E6=9C=AA=E6=88=90=E5=93=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/OperationService.py | 87 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/service/OperationService.py b/service/OperationService.py index 206e741..1237811 100644 --- a/service/OperationService.py +++ b/service/OperationService.py @@ -389,4 +389,89 @@ class OperationService: result = self.db.execute_write_and_return(cypher, {"id": rel_id}) return {"success": True, "msg": "删除成功"} if result else {"success": False, "msg": "关系不存在"} except Exception as e: - return {"success": False, "msg": f"删除失败: {str(e)}"} \ No newline at end of file + return {"success": False, "msg": f"删除失败: {str(e)}"} + + # --- 7. 导出功能 --- + def export_nodes_to_json(self, label=None, name=None): + """ + 按照条件导出节点,确保包含 identity, elementId, labels, properties 等所有原始字段 + """ + try: + conditions = [] + params = {} + + # 构建过滤条件(复用查询逻辑,但去掉分页) + if name: + params["name"] = unquote(str(name)).strip() + conditions.append("n.name CONTAINS $name") + + lb_clause = "" + if label and label not in ["全部", ""]: + # 为了保证原生对象的完整性,这里直接 MATCH 标签 + lb_clause = f":`{label}`" + + where_clause = "WHERE " + " AND ".join(conditions) if conditions else "" + + # 注意:这里 RETURN n,返回的是整个节点对象 + cypher = f"MATCH (n{lb_clause}) {where_clause} RETURN n" + + raw_data = self.db.execute_read(cypher, params) + + export_items = [] + for row in raw_data: + node = row['n'] + # 核心逻辑:提取 Neo4j 节点对象的所有原生属性 + node_data = { + "identity": node.id, # 对应你截图中的 identity (旧版 ID) + "elementId": node.element_id, # 对应你截图中的 elementId (新版 ID) + "labels": list(node.labels), + "properties": dict(node.items()) + } + export_items.append(node_data) + + return {"success": True, "data": export_items} + except Exception as e: + traceback.print_exc() + return {"success": False, "msg": f"导出节点失败: {str(e)}"} + + def export_relationships_to_json(self, source=None, target=None, rel_type=None): + """ + 按照条件导出关系,确保包含起始/结束节点信息及完整属性 + """ + try: + conditions = [] + params = {} + if source: + params["source"] = unquote(str(source)).strip() + conditions.append("a.name CONTAINS $source") + if target: + params["target"] = unquote(str(target)).strip() + conditions.append("b.name CONTAINS $target") + if rel_type and rel_type not in ["全部", ""]: + conditions.append(f"type(r) = $rel_type") + params["rel_type"] = rel_type + + where_clause = "WHERE " + " AND ".join(conditions) if conditions else "" + + # 返回关系对象 r 以及起止节点的 elementId 以便追溯 + cypher = f"MATCH (a)-[r]->(b) {where_clause} RETURN r, elementId(a) as startNode, elementId(b) as endNode" + + raw_data = self.db.execute_read(cypher, params) + + export_items = [] + for row in raw_data: + rel = row['r'] + rel_data = { + "identity": rel.id, + "elementId": rel.element_id, + "type": rel.type, + "startNodeElementId": row['startNode'], + "endNodeElementId": row['endNode'], + "properties": dict(rel.items()) + } + export_items.append(rel_data) + + return {"success": True, "data": export_items} + except Exception as e: + traceback.print_exc() + return {"success": False, "msg": f"导出关系失败: {str(e)}"} \ No newline at end of file From 350402c8a90f629ee1a7f8cebf971fa764a55116 Mon Sep 17 00:00:00 2001 From: hanyuqing <1106611654@qq.com> Date: Wed, 7 Jan 2026 09:54:27 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=B7=A5=E5=85=B7=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vue/src/system/GraphStyle.vue | 101 +++++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 20 deletions(-) diff --git a/vue/src/system/GraphStyle.vue b/vue/src/system/GraphStyle.vue index 4b74f7a..81fdd52 100644 --- a/vue/src/system/GraphStyle.vue +++ b/vue/src/system/GraphStyle.vue @@ -684,19 +684,15 @@ export default { this.saveDialogVisible = true; }, async confirmSave() { - if (!this.saveForm.group_name || !this - .saveForm.group_name.trim()) { - return this.$message.warning("请选择或输入方案名称" - ); + if (!this.saveForm.group_name || !this.saveForm.group_name.trim()) { + return ElMessage.warning("请选择或输入方案名称"); } - if (!this.saveForm.canvas_name || !this - .saveForm.canvas_name.trim()) { - return this.$message.warning("请输入配置名称" - ); + if (!this.saveForm.canvas_name || !this.saveForm.canvas_name.trim()) { + return ElMessage.warning("请输入配置名称"); } const payload = { - canvas_name: this.saveForm.canvas_name, - group_name: this.saveForm.group_name, + canvas_name: this.saveForm.canvas_name.trim(), + group_name: this.saveForm.group_name.trim(), current_label: this.activeTags, styles: {...this.tagStyles[tagToLabelMap[this.activeTags]]} }; @@ -704,6 +700,8 @@ export default { if (res.code === 200) { ElMessage.success("保存成功"); this.saveDialogVisible = false; + this.saveForm.canvas_name = ''; + this.saveForm.group_name = ''; this.resetAllTagsToDefault(); this.fetchConfigs(); } @@ -725,51 +723,114 @@ export default { this.updateAllElements(); ElMessage.info(`已重置【${this.activeTags}】样式`); }, + + // --- 修改点:单个配置删除增加判断 --- async deleteSingleConfig(id) { + if (this.usingConfigIds.includes(id)) { + return ElMessage.error("该配置正在应用中,请取消应用或切换方案后再删除"); + } + try { await ElMessageBox.confirm('确定删除此配置吗?', '提示'); const res = await deleteGraphStyle(id); if (res.code === 200) { - this.usingConfigIds = this.usingConfigIds.filter(cid => cid !== id); + ElMessage.success("删除成功"); this.fetchConfigs(); - this.updateAllElements(); } } catch (err) {} }, + + // --- 修改点:方案组删除增加判断 --- async deleteGroup(groupId) { + const group = this.styleGroups.find(g => g.id === groupId); + if (!group) return; + + // 1. 判断是否正在应用 + const isGroupUsing = group.configs.some(c => this.usingConfigIds.includes(c.id)); + if (isGroupUsing || group.is_active) { + return ElMessage.error("该方案中包含正在应用的配置,无法删除"); + } + + // 2. 判断是否是最后一个方案 + if (this.styleGroups.length <= 1) { + return ElMessage.error("系统至少需保留一个方案,无法全部删除"); + } + try { await ElMessageBox.confirm('确定删除整个方案吗?', '提示'); const res = await deleteGraphStyleGroup(groupId); if (res.code === 200) { + ElMessage.success("方案已删除"); this.fetchConfigs(); - this.updateAllElements(); } } catch (err) {} }, + + // --- 修改点:批量删除增加核心判断逻辑 --- async handleUnifiedBatchDelete() { + // 1. 基础判断 + if (this.checkedConfigIds.length === 0 && this.checkedGroupIds.length === 0) { + return ElMessage.warning("请先勾选要删除的项目"); + } + + // 2. 正在应用状态判断 + const isAnyCheckedConfigUsing = this.checkedConfigIds.some(id => this.usingConfigIds.includes(id)); + const isAnyCheckedGroupUsing = this.styleGroups + .filter(g => this.checkedGroupIds.includes(g.id)) + .some(g => g.configs.some(c => this.usingConfigIds.includes(c.id))); + + if (isAnyCheckedConfigUsing || isAnyCheckedGroupUsing) { + return ElMessageBox.alert( + '选中的项目中包含“正在应用”的配置,请先取消应用后再执行删除操作。', + '无法执行删除', + { type: 'error', confirmButtonText: '我知道了' } + ); + } + + // 3. 最小保留数判断 (针对方案组) + if (this.checkedGroupIds.length >= this.styleGroups.length && this.styleGroups.length > 0) { + return ElMessage.error("系统至少需要保留一个方案,请勿全部勾选删除"); + } + try { await ElMessageBox.confirm( - '确定执行批量删除吗?', - '批量删除', + '确定执行批量删除吗?此操作不可恢复。', + '批量删除确认', { - confirmButtonText: '确定', + confirmButtonText: '确定删除', cancelButtonText: '取消', type: 'warning' } ); - for (const gid of this.checkedGroupIds) await deleteGraphStyleGroup(gid); - if (this.checkedConfigIds.length > 0) await batchDeleteGraphStyle({ids: this.checkedConfigIds}); + + // 执行删除 + if (this.checkedGroupIds.length > 0) { + for (const gid of this.checkedGroupIds) { + await deleteGraphStyleGroup(gid); + } + } + + if (this.checkedConfigIds.length > 0) { + await batchDeleteGraphStyle({ids: this.checkedConfigIds}); + } + + ElMessage.success("批量删除成功"); this.clearSelection(); this.fetchConfigs(); this.updateAllElements(); - } catch (e) {} + } catch (e) { + console.log("用户取消或删除失败", e); + } }, + clearSelection() { this.checkedConfigIds = []; this.checkedGroupIds = []; }, handleResize() { - if (this._graph && this.$refs.graphContainer) this._graph.setSize(this.$refs.graphContainer.clientWidth, this.$refs.graphContainer.clientHeight); + if (this._graph && this.$refs.graphContainer) { + this._graph.setSize(this.$refs.graphContainer.clientWidth, this.$refs.graphContainer.clientHeight); + } } } }