9 changed files with 1282 additions and 391 deletions
@ -0,0 +1,132 @@ |
|||
# controller/GraphStyleController.py |
|||
import json |
|||
from robyn import jsonify, Response |
|||
from app import app |
|||
from service.GraphStyleService import GraphStyleService |
|||
|
|||
|
|||
# --- 核心工具函数:解决乱码 --- |
|||
def create_response(status_code, data_dict): |
|||
""" |
|||
统一响应格式封装,强制使用 UTF-8 防止中文乱码。 |
|||
""" |
|||
return Response( |
|||
status_code=status_code, |
|||
description=json.dumps(data_dict, ensure_ascii=False), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
|
|||
|
|||
@app.post("/api/graph/style/save") |
|||
async def save_style_config(request): |
|||
"""保存配置接口 - 升级版:支持分组名""" |
|||
try: |
|||
body = request.json() |
|||
canvas_name = body.get('canvas_name') |
|||
current_label = body.get('current_label') |
|||
styles = body.get('styles') |
|||
# 新增:接收分组名称(字符串) |
|||
group_name = body.get('group_name') |
|||
|
|||
if not all([canvas_name, current_label, styles]): |
|||
return create_response(200, {"code": 400, "msg": "参数不完整"}) |
|||
|
|||
# 调用 Service,逻辑内部处理:组名存在则用,不存在则建 |
|||
success = GraphStyleService.save_config(canvas_name, current_label, styles, group_name) |
|||
if success: |
|||
return create_response(200, {"code": 200, "msg": "保存成功"}) |
|||
else: |
|||
return create_response(200, {"code": 500, "msg": "保存失败"}) |
|||
except Exception as e: |
|||
return create_response(200, {"code": 500, "msg": f"系统异常: {str(e)}"}) |
|||
|
|||
|
|||
@app.get("/api/graph/style/list/grouped") |
|||
async def get_grouped_style_list(request): |
|||
"""获取【分组嵌套】格式的配置列表(用于右侧折叠面板)""" |
|||
try: |
|||
# 调用 Service 的嵌套聚合方法 |
|||
data = GraphStyleService.get_grouped_configs() |
|||
return create_response(200, {"code": 200, "data": data, "msg": "查询成功"}) |
|||
except Exception as e: |
|||
return create_response(200, {"code": 500, "msg": f"查询异常: {str(e)}"}) |
|||
|
|||
|
|||
@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": "查询成功"}) |
|||
except Exception as e: |
|||
return create_response(200, {"code": 500, "msg": f"查询异常: {str(e)}"}) |
|||
|
|||
|
|||
@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": "查询成功"}) |
|||
except Exception as e: |
|||
return create_response(200, {"code": 500, "msg": f"查询异常: {str(e)}"}) |
|||
|
|||
|
|||
@app.post("/api/graph/style/delete") |
|||
async def delete_style_config(request): |
|||
"""删除单条画布配置""" |
|||
try: |
|||
body = request.json() |
|||
config_id = body.get('id') |
|||
|
|||
if not config_id: |
|||
return create_response(200, {"code": 400, "msg": "缺少ID"}) |
|||
|
|||
success = GraphStyleService.delete_config(config_id) |
|||
if success: |
|||
return create_response(200, {"code": 200, "msg": "删除成功"}) |
|||
else: |
|||
return create_response(200, {"code": 500, "msg": "删除失败"}) |
|||
except Exception as e: |
|||
return create_response(200, {"code": 500, "msg": f"操作异常: {str(e)}"}) |
|||
|
|||
|
|||
@app.post("/api/graph/style/group/delete") |
|||
async def delete_style_group(request): |
|||
"""删除整个方案组及其下属所有配置""" |
|||
try: |
|||
body = request.json() |
|||
group_id = body.get('group_id') |
|||
|
|||
if not group_id: |
|||
return create_response(200, {"code": 400, "msg": "缺少分组ID"}) |
|||
|
|||
success = GraphStyleService.delete_group(group_id) |
|||
if success: |
|||
return create_response(200, {"code": 200, "msg": "方案组已彻底删除"}) |
|||
else: |
|||
return create_response(200, {"code": 500, "msg": "方案组删除失败"}) |
|||
except Exception as e: |
|||
return create_response(200, {"code": 500, "msg": f"操作异常: {str(e)}"}) |
|||
|
|||
|
|||
@app.post("/api/graph/style/batch_delete") |
|||
async def batch_delete_style(request): |
|||
"""批量删除配置接口""" |
|||
try: |
|||
body = request.json() |
|||
config_ids = body.get('ids') |
|||
|
|||
if isinstance(config_ids, str): |
|||
try: |
|||
config_ids = json.loads(config_ids) |
|||
except: |
|||
pass |
|||
|
|||
if not config_ids or not isinstance(config_ids, list): |
|||
return create_response(200, {"code": 400, "msg": "参数格式错误"}) |
|||
|
|||
count = GraphStyleService.batch_delete_configs(config_ids) |
|||
return create_response(200, {"code": 200, "msg": f"成功删除 {count} 条配置", "count": count}) |
|||
except Exception as e: |
|||
return create_response(200, {"code": 500, "msg": f"批量删除异常: {str(e)}"}) |
|||
@ -0,0 +1,139 @@ |
|||
# service/GraphStyleService.py |
|||
import json |
|||
from util.mysql_utils import mysql_client |
|||
|
|||
class GraphStyleService: |
|||
@staticmethod |
|||
def save_config(canvas_name: str, current_label: str, styles_dict: dict, group_name: str = None) -> bool: |
|||
""" |
|||
保存图谱样式配置(增强版:自动处理分组逻辑) |
|||
:param canvas_name: 画布显示名称 |
|||
:param current_label: 针对的标签名称 |
|||
:param styles_dict: 样式字典 |
|||
:param group_name: 分组名称(前端传来的字符串) |
|||
""" |
|||
# 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: |
|||
# 如果存在,直接使用已有 ID |
|||
target_group_id = existing_group[0]['id'] |
|||
else: |
|||
# 如果不存在,新建一个组 |
|||
create_group_sql = "INSERT INTO graph_style_groups (group_name) VALUES (%s)" |
|||
mysql_client.execute_update(create_group_sql, (group_name,)) |
|||
# 获取新生成的 ID |
|||
get_id_sql = "SELECT LAST_INSERT_ID() as last_id" |
|||
id_res = mysql_client.execute_query(get_id_sql) |
|||
target_group_id = id_res[0]['last_id'] if id_res else 1 |
|||
|
|||
# 2. 转换样式 JSON |
|||
config_json = json.dumps(styles_dict, ensure_ascii=False) |
|||
|
|||
# 3. 插入配置表(关联 target_group_id) |
|||
sql = """ |
|||
INSERT INTO graph_configs (canvas_name, current_label, config_json, group_id) |
|||
VALUES (%s, %s, %s, %s) |
|||
""" |
|||
affected_rows = mysql_client.execute_update(sql, (canvas_name, current_label, config_json, target_group_id)) |
|||
return affected_rows > 0 |
|||
|
|||
@staticmethod |
|||
def get_grouped_configs() -> list: |
|||
""" |
|||
核心优化:获取嵌套结构的方案列表 (Group -> Configs) |
|||
用于前端右侧折叠面板展示 |
|||
""" |
|||
# 1. 查询所有方案组 |
|||
groups_sql = "SELECT id, group_name FROM graph_style_groups ORDER BY id ASC" |
|||
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. 内存聚合:将配置项塞进对应的组 |
|||
# 先处理配置项的 JSON 和 时间 |
|||
for conf in configs: |
|||
if conf.get('config_json'): |
|||
try: |
|||
conf['styles'] = json.loads(conf['config_json']) |
|||
except: |
|||
conf['styles'] = {} |
|||
del conf['config_json'] |
|||
|
|||
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: |
|||
# 找到属于该组的所有配置 |
|||
g_children = [c for c in configs if c['group_id'] == g['id']] |
|||
g['configs'] = g_children |
|||
# 增加一个前端控制开关用的字段 |
|||
g['expanded'] = False |
|||
result.append(g) |
|||
|
|||
return result |
|||
|
|||
@staticmethod |
|||
def get_all_configs() -> list: |
|||
"""保持原有的扁平查询功能,仅增加 group_id 字段返回""" |
|||
sql = "SELECT id, group_id, canvas_name, current_label, config_json, create_time FROM graph_configs ORDER BY create_time DESC" |
|||
rows = mysql_client.execute_query(sql) |
|||
if not rows: return [] |
|||
|
|||
for row in rows: |
|||
if row.get('config_json'): |
|||
try: row['styles'] = json.loads(row['config_json']) |
|||
except: row['styles'] = {} |
|||
del row['config_json'] |
|||
if row.get('create_time') and not isinstance(row['create_time'], str): |
|||
row['create_time'] = row['create_time'].strftime('%Y-%m-%d %H:%M:%S') |
|||
return rows |
|||
|
|||
@staticmethod |
|||
def delete_group(group_id: int) -> bool: |
|||
""" |
|||
逻辑级联删除:删除方案组及其关联的所有配置 |
|||
""" |
|||
# 1. 删除组下的所有配置 |
|||
del_configs_sql = "DELETE FROM graph_configs WHERE group_id = %s" |
|||
mysql_client.execute_update(del_configs_sql, (group_id,)) |
|||
|
|||
# 2. 删除组本身 |
|||
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 |
|||
|
|||
if not clean_ids: return 0 |
|||
placeholders = ', '.join(['%s'] * len(clean_ids)) |
|||
sql = f"DELETE FROM graph_configs WHERE id IN ({placeholders})" |
|||
return mysql_client.execute_update(sql, tuple(clean_ids)) |
|||
|
|||
@staticmethod |
|||
def get_group_list() -> list: |
|||
"""单独获取方案名称列表,供前端下拉框使用""" |
|||
sql = "SELECT id, group_name FROM graph_style_groups ORDER BY create_time DESC" |
|||
return mysql_client.execute_query(sql) or [] |
|||
@ -0,0 +1,96 @@ |
|||
// vue/src/api/style.js
|
|||
import request from '@/utils/request'; |
|||
|
|||
/** |
|||
* 保存图谱样式配置 |
|||
* @param {Object} data { canvas_name, current_label, styles, group_name } |
|||
* 说明:group_name 为字符串,后端会自动判断是使用已有组还是新建组 |
|||
*/ |
|||
export function saveGraphStyle(data) { |
|||
return request({ |
|||
url: '/api/graph/style/save', |
|||
method: 'post', |
|||
data: data, |
|||
headers: { |
|||
'Content-Type': 'application/json' |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 获取【分组嵌套】格式的样式配置列表 (核心新增) |
|||
* 用于右侧折叠面板渲染:Group -> Configs |
|||
*/ |
|||
export function getGroupedGraphStyleList() { |
|||
return request({ |
|||
url: '/api/graph/style/list/grouped', |
|||
method: 'get' |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 获取所有已存在的方案组名称列表 |
|||
* 用于保存配置弹窗中的下拉选择框 |
|||
*/ |
|||
export function getGraphStyleGroups() { |
|||
return request({ |
|||
url: '/api/graph/style/groups', |
|||
method: 'get' |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 获取所有图谱样式配置列表 |
|||
* 保留此接口用于兼容旧版逻辑或后台管理 |
|||
*/ |
|||
export function getGraphStyleList() { |
|||
return request({ |
|||
url: '/api/graph/style/list', |
|||
method: 'get' |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 删除整个方案组及其下属所有配置 |
|||
* @param {Number} group_id 分组ID |
|||
*/ |
|||
export function deleteGraphStyleGroup(group_id) { |
|||
return request({ |
|||
url: '/api/graph/style/group/delete', |
|||
method: 'post', |
|||
data: { group_id }, |
|||
headers: { |
|||
'Content-Type': 'application/json' |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 删除指定的单个画布样式配置 |
|||
* @param {Number} id 配置ID |
|||
*/ |
|||
export function deleteGraphStyle(id) { |
|||
return request({ |
|||
url: '/api/graph/style/delete', |
|||
method: 'post', |
|||
data: { id }, |
|||
headers: { |
|||
'Content-Type': 'application/json' |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 批量删除多个画布样式配置 |
|||
* @param {Object} payload { ids: [1, 2, 3] } |
|||
*/ |
|||
export function batchDeleteGraphStyle(payload) { |
|||
return request({ |
|||
url: '/api/graph/style/batch_delete', |
|||
method: 'post', |
|||
data: payload, |
|||
headers: { |
|||
'Content-Type': 'application/json' |
|||
} |
|||
}); |
|||
} |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue