From 27cefb08be5636392032995ba79317900433e6f5 Mon Sep 17 00:00:00 2001 From: hanyuqing <1106611654@qq.com> Date: Wed, 7 Jan 2026 15:26:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E5=85=B7=E9=A1=B5=E9=9D=A2=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/GraphStyleController.py | 36 +- service/GraphStyleService.py | 119 ++++--- vue/src/system/GraphStyle.vue | 665 +++++++++++++++++++------------------ 3 files changed, 419 insertions(+), 401 deletions(-) diff --git a/controller/GraphStyleController.py b/controller/GraphStyleController.py index 7c63874..f601d03 100644 --- a/controller/GraphStyleController.py +++ b/controller/GraphStyleController.py @@ -19,29 +19,38 @@ def create_response(status_code, data_dict): @app.post("/api/graph/style/save") async def save_style_config(request): - """保存配置接口 - 修复版:支持移动与更新逻辑""" + """ + 保存配置接口 - 增强防跑偏版 + 逻辑: + 1. 如果 body 中包含 is_auto_save: true,则强制忽略 group_name,防止自动保存篡改归属。 + 2. 如果是普通保存或移动,则正常传递 group_name。 + """ try: body = request.json() - # 1. 核心修改:接收前端传来的 id - # 当执行“移动”操作时,前端会传 config.id;当执行“保存当前配置”时,id 为空 config_id = body.get('id') - canvas_name = body.get('canvas_name') current_label = body.get('current_label') styles = body.get('styles') + + # 核心改动:获取 group_name group_name = body.get('group_name') + # 增加一个前端标识:如果是实时同步(防抖保存),前端可以传这个字段 + is_auto_save = body.get('is_auto_save', False) + if not all([canvas_name, current_label, styles]): return create_response(200, {"code": 400, "msg": "参数不完整"}) - # 2. 核心修改:将 config_id 传给 Service 层 - # 这样 Service 就能根据是否有 id 来判断是执行 UPDATE 还是 INSERT + # 如果是自动保存模式,显式清空 group_name,强制 Service 进入“仅更新样式”逻辑 + final_group_name = None if is_auto_save else group_name + + # 将处理后的参数传给 Service 层 success = GraphStyleService.save_config( canvas_name=canvas_name, current_label=current_label, styles_dict=styles, - group_name=group_name, + group_name=final_group_name, config_id=config_id ) @@ -55,9 +64,8 @@ async def save_style_config(request): @app.get("/api/graph/style/list/grouped") async def get_grouped_style_list(request): - """获取【分组嵌套】格式的配置列表(用于右侧折叠面板)""" + """获取【分组嵌套】格式的配置列表""" try: - # 调用 Service 的嵌套聚合方法,现在内部已包含 is_active/is_default 逻辑 data = GraphStyleService.get_grouped_configs() return create_response(200, {"code": 200, "data": data, "msg": "查询成功"}) except Exception as e: @@ -66,7 +74,7 @@ async def get_grouped_style_list(request): @app.post("/api/graph/style/group/apply") async def apply_style_group(request): - """应用全案:将某个方案组设为当前激活状态""" + """应用全案""" try: body = request.json() group_id = body.get('group_id') @@ -76,7 +84,7 @@ async def apply_style_group(request): success = GraphStyleService.apply_group_all(group_id) if success: - return create_response(200, {"code": 200, "msg": "方案已成功应用全案"}) + return create_response(200, {"code": 200, "msg": "方案已成功应用"}) else: return create_response(200, {"code": 500, "msg": "应用全案失败"}) except Exception as e: @@ -85,7 +93,7 @@ async def apply_style_group(request): @app.post("/api/graph/style/group/set_default") async def set_default_style_group(request): - """设为默认:将某个方案组设为页面初始化的默认配置""" + """设为系统初始默认方案""" try: body = request.json() group_id = body.get('group_id') @@ -104,7 +112,7 @@ async def set_default_style_group(request): @app.get("/api/graph/style/groups") async def get_group_names(request): - """获取所有已存在的方案组列表(用于保存弹窗的下拉选择)""" + """获取所有已存在的方案组列表""" try: data = GraphStyleService.get_group_list() return create_response(200, {"code": 200, "data": data, "msg": "查询成功"}) @@ -114,7 +122,7 @@ async def get_group_names(request): @app.get("/api/graph/style/list") async def get_style_list(request): - """获取原始扁平配置列表(保留兼容性)""" + """获取原始扁平配置列表""" try: data = GraphStyleService.get_all_configs() return create_response(200, {"code": 200, "data": data, "msg": "查询成功"}) diff --git a/service/GraphStyleService.py b/service/GraphStyleService.py index 1246eb8..e4202ad 100644 --- a/service/GraphStyleService.py +++ b/service/GraphStyleService.py @@ -7,38 +7,60 @@ class GraphStyleService: @staticmethod def save_config(canvas_name: str, current_label: str, styles_dict: dict, group_name: str = None, config_id: int = None) -> bool: """ - 保存图谱样式配置(修复版:支持移动与更新逻辑) + 保存图谱样式配置(修复版:防止自动保存导致的分组乱跑) """ - # 1. 处理分组逻辑 - if not group_name or group_name.strip() == "": - group_name = "默认方案" - - # 检查/创建 目标方案组 - check_group_sql = "SELECT id FROM graph_style_groups WHERE group_name = %s LIMIT 1" - existing_group = mysql_client.execute_query(check_group_sql, (group_name,)) - - if existing_group: - target_group_id = existing_group[0]['id'] - else: - create_group_sql = "INSERT INTO graph_style_groups (group_name, is_active, is_default) VALUES (%s, %s, %s)" - mysql_client.execute_update(create_group_sql, (group_name, False, False)) - target_group_id = mysql_client.execute_query("SELECT LAST_INSERT_ID() as last_id")[0]['last_id'] - # 2. 转换样式 JSON config_json = json.dumps(styles_dict, ensure_ascii=False) - # 3. 【核心修复】:判断是更新(移动)还是新建 + # 3. 【核心修改点】:区分 更新 还是 新建 if config_id: - # 如果带了 ID,说明是“移动”或“修改”,执行 UPDATE - # 这样 group_id 会被更新,且不会产生新记录,配置就从原方案“消失”并出现在新方案了 - sql = """ - UPDATE graph_configs - SET canvas_name = %s, current_label = %s, config_json = %s, group_id = %s - WHERE id = %s - """ - affected_rows = mysql_client.execute_update(sql, (canvas_name, current_label, config_json, target_group_id, config_id)) + # --- 更新逻辑 --- + # 如果带了 ID,我们要极其谨慎地处理 group_id,防止在自动保存时被误改 + + # A. 如果调用者明确传了 group_name,说明是“移动”或“初次保存到某组” + if group_name and group_name.strip() != "": + # 检查/创建 目标方案组 + check_group_sql = "SELECT id FROM graph_style_groups WHERE group_name = %s LIMIT 1" + existing_group = mysql_client.execute_query(check_group_sql, (group_name,)) + + if existing_group: + target_group_id = existing_group[0]['id'] + else: + create_group_sql = "INSERT INTO graph_style_groups (group_name, is_active, is_default) VALUES (%s, %s, %s)" + mysql_client.execute_update(create_group_sql, (group_name, False, False)) + target_group_id = mysql_client.execute_query("SELECT LAST_INSERT_ID() as last_id")[0]['last_id'] + + # 执行带分组更新的 SQL + sql = """ + UPDATE graph_configs + SET canvas_name = %s, current_label = %s, config_json = %s, group_id = %s + WHERE id = %s + """ + affected_rows = mysql_client.execute_update(sql, (canvas_name, current_label, config_json, target_group_id, config_id)) + else: + # B. 如果没有传 group_name,说明是“实时自动保存”,严禁修改 group_id + # 这样即使前端变量乱了,数据库的分组也不会变 + sql = """ + UPDATE graph_configs + SET canvas_name = %s, current_label = %s, config_json = %s + WHERE id = %s + """ + affected_rows = mysql_client.execute_update(sql, (canvas_name, current_label, config_json, config_id)) else: - # 如果没有 ID,说明是点“保存”按钮新建的,执行 INSERT + # --- 新建逻辑 --- + # 新建时必须有组名,默认“默认方案” + if not group_name or group_name.strip() == "": + group_name = "默认方案" + + check_group_sql = "SELECT id FROM graph_style_groups WHERE group_name = %s LIMIT 1" + existing_group = mysql_client.execute_query(check_group_sql, (group_name,)) + if existing_group: + target_group_id = existing_group[0]['id'] + else: + create_group_sql = "INSERT INTO graph_style_groups (group_name, is_active, is_default) VALUES (%s, %s, %s)" + mysql_client.execute_update(create_group_sql, (group_name, False, False)) + target_group_id = mysql_client.execute_query("SELECT LAST_INSERT_ID() as last_id")[0]['last_id'] + sql = """ INSERT INTO graph_configs (canvas_name, current_label, config_json, group_id) VALUES (%s, %s, %s, %s) @@ -50,10 +72,8 @@ class GraphStyleService: @staticmethod def get_grouped_configs() -> list: """ - 核心优化:获取嵌套结构的方案列表 - 增加 is_active 和 is_default 字段支持,并按默认/激活状态排序 + 获取嵌套结构的方案列表,按默认/激活状态排序 """ - # 1. 查询所有方案组:让默认方案排在最上面 groups_sql = """ SELECT id, group_name, is_active, is_default FROM graph_style_groups @@ -61,11 +81,9 @@ class GraphStyleService: """ groups = mysql_client.execute_query(groups_sql) or [] - # 2. 查询所有配置项 configs_sql = "SELECT id, group_id, canvas_name, current_label, config_json, create_time FROM graph_configs" configs = mysql_client.execute_query(configs_sql) or [] - # 3. 内存聚合 for conf in configs: if conf.get('config_json'): try: @@ -77,18 +95,12 @@ class GraphStyleService: if conf.get('create_time') and not isinstance(conf['create_time'], str): conf['create_time'] = conf['create_time'].strftime('%Y-%m-%d %H:%M:%S') - # 组装数据结构 result = [] for g in groups: - # 兼容 MySQL 布尔值转换 (某些驱动返回 0/1) g['is_active'] = bool(g['is_active']) g['is_default'] = bool(g['is_default']) - - # 找到属于该组的所有配置 g_children = [c for c in configs if c['group_id'] == g['id']] g['configs'] = g_children - - # 如果是激活状态,默认让它在前端展开 g['expanded'] = g['is_active'] result.append(g) @@ -96,16 +108,10 @@ class GraphStyleService: @staticmethod def apply_group_all(group_id: int) -> bool: - """ - 核心新增:应用全案 - 逻辑:将该组设为 is_active=true,其余所有组设为 false - """ + """应用全案:设置激活状态""" try: - # 1. 全部重置为非激活 reset_sql = "UPDATE graph_style_groups SET is_active = %s" mysql_client.execute_update(reset_sql, (False,)) - - # 2. 激活指定组 apply_sql = "UPDATE graph_style_groups SET is_active = %s WHERE id = %s" affected_rows = mysql_client.execute_update(apply_sql, (True, group_id)) return affected_rows > 0 @@ -115,16 +121,10 @@ class GraphStyleService: @staticmethod def set_default_group(group_id: int) -> bool: - """ - 核心新增:设为系统初始默认方案 - 逻辑:唯一性切换 - """ + """设为系统初始默认方案""" try: - # 1. 全部取消默认 reset_sql = "UPDATE graph_style_groups SET is_default = %s" mysql_client.execute_update(reset_sql, (False,)) - - # 2. 设置新的默认项 set_sql = "UPDATE graph_style_groups SET is_default = %s WHERE id = %s" affected_rows = mysql_client.execute_update(set_sql, (True, group_id)) return affected_rows > 0 @@ -134,7 +134,7 @@ class GraphStyleService: @staticmethod def get_all_configs() -> list: - """获取扁平查询,增加关联方案的激活状态""" + """获取扁平查询""" sql = """ SELECT c.id, c.group_id, c.canvas_name, c.current_label, c.config_json, c.create_time, g.is_active FROM graph_configs c @@ -158,30 +158,27 @@ class GraphStyleService: @staticmethod def delete_group(group_id: int) -> bool: - """逻辑级联删除""" + """级联删除""" del_configs_sql = "DELETE FROM graph_configs WHERE group_id = %s" mysql_client.execute_update(del_configs_sql, (group_id,)) - del_group_sql = "DELETE FROM graph_style_groups WHERE id = %s" affected_rows = mysql_client.execute_update(del_group_sql, (group_id,)) return affected_rows > 0 @staticmethod def delete_config(config_id: int) -> bool: - """删除单个配置""" + """删除配置""" sql = "DELETE FROM graph_configs WHERE id = %s" affected_rows = mysql_client.execute_update(sql, (config_id,)) return affected_rows > 0 @staticmethod def batch_delete_configs(config_ids: list) -> int: - """批量删除配置""" + """批量删除""" if not config_ids: return 0 try: clean_ids = [int(cid) for cid in config_ids if str(cid).isdigit()] - except: - return 0 - + except: return 0 if not clean_ids: return 0 placeholders = ', '.join(['%s'] * len(clean_ids)) sql = f"DELETE FROM graph_configs WHERE id IN ({placeholders})" @@ -189,6 +186,6 @@ class GraphStyleService: @staticmethod def get_group_list() -> list: - """单独获取方案列表,增加状态返回""" - sql = "SELECT id, group_name, is_active, is_default FROM graph_style_groups ORDER BY is_default DESC, create_time DESC" + """获取方案列表""" + sql = "SELECT id, group_name, is_active, is_default FROM graph_style_groups ORDER BY is_default DESC, id DESC" return mysql_client.execute_query(sql) or [] \ No newline at end of file diff --git a/vue/src/system/GraphStyle.vue b/vue/src/system/GraphStyle.vue index 62520bc..b5fa14d 100644 --- a/vue/src/system/GraphStyle.vue +++ b/vue/src/system/GraphStyle.vue @@ -213,8 +213,7 @@ size="small" type="info" disabled - plain - @click.stop>已应用 + plain>已应用 @@ -231,10 +230,10 @@