Browse Source

Merge branch 'mh' of http://124.70.32.114:3100/hanyuqing/KGPython into hanyuqing

# Conflicts:
#	vue/src/system/GraphStyle.vue
hanyuqing
hanyuqing 3 months ago
parent
commit
34e439d953
  1. 36
      controller/GraphStyleController.py
  2. 119
      service/GraphStyleService.py
  3. 653
      vue/src/system/GraphStyle.vue

36
controller/GraphStyleController.py

@ -19,29 +19,38 @@ def create_response(status_code, data_dict):
@app.post("/api/graph/style/save") @app.post("/api/graph/style/save")
async def save_style_config(request): async def save_style_config(request):
"""保存配置接口 - 修复版:支持移动与更新逻辑""" """
保存配置接口 - 增强防跑偏版
逻辑
1. 如果 body 中包含 is_auto_save: true则强制忽略 group_name防止自动保存篡改归属
2. 如果是普通保存或移动则正常传递 group_name
"""
try: try:
body = request.json() body = request.json()
# 1. 核心修改:接收前端传来的 id
# 当执行“移动”操作时,前端会传 config.id;当执行“保存当前配置”时,id 为空
config_id = body.get('id') config_id = body.get('id')
canvas_name = body.get('canvas_name') canvas_name = body.get('canvas_name')
current_label = body.get('current_label') current_label = body.get('current_label')
styles = body.get('styles') styles = body.get('styles')
# 核心改动:获取 group_name
group_name = body.get('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]): if not all([canvas_name, current_label, styles]):
return create_response(200, {"code": 400, "msg": "参数不完整"}) return create_response(200, {"code": 400, "msg": "参数不完整"})
# 2. 核心修改:将 config_id 传给 Service 层 # 如果是自动保存模式,显式清空 group_name,强制 Service 进入“仅更新样式”逻辑
# 这样 Service 就能根据是否有 id 来判断是执行 UPDATE 还是 INSERT final_group_name = None if is_auto_save else group_name
# 将处理后的参数传给 Service 层
success = GraphStyleService.save_config( success = GraphStyleService.save_config(
canvas_name=canvas_name, canvas_name=canvas_name,
current_label=current_label, current_label=current_label,
styles_dict=styles, styles_dict=styles,
group_name=group_name, group_name=final_group_name,
config_id=config_id config_id=config_id
) )
@ -55,9 +64,8 @@ async def save_style_config(request):
@app.get("/api/graph/style/list/grouped") @app.get("/api/graph/style/list/grouped")
async def get_grouped_style_list(request): async def get_grouped_style_list(request):
"""获取【分组嵌套】格式的配置列表(用于右侧折叠面板)""" """获取【分组嵌套】格式的配置列表"""
try: try:
# 调用 Service 的嵌套聚合方法,现在内部已包含 is_active/is_default 逻辑
data = GraphStyleService.get_grouped_configs() data = GraphStyleService.get_grouped_configs()
return create_response(200, {"code": 200, "data": data, "msg": "查询成功"}) return create_response(200, {"code": 200, "data": data, "msg": "查询成功"})
except Exception as e: except Exception as e:
@ -75,7 +83,7 @@ async def get_active_style(request):
@app.post("/api/graph/style/group/apply") @app.post("/api/graph/style/group/apply")
async def apply_style_group(request): async def apply_style_group(request):
"""应用全案:将某个方案组设为当前激活状态""" """应用全案"""
try: try:
body = request.json() body = request.json()
group_id = body.get('group_id') group_id = body.get('group_id')
@ -85,7 +93,7 @@ async def apply_style_group(request):
success = GraphStyleService.apply_group_all(group_id) success = GraphStyleService.apply_group_all(group_id)
if success: if success:
return create_response(200, {"code": 200, "msg": "方案已成功应用全案"}) return create_response(200, {"code": 200, "msg": "方案已成功应用"})
else: else:
return create_response(200, {"code": 500, "msg": "应用全案失败"}) return create_response(200, {"code": 500, "msg": "应用全案失败"})
except Exception as e: except Exception as e:
@ -94,7 +102,7 @@ async def apply_style_group(request):
@app.post("/api/graph/style/group/set_default") @app.post("/api/graph/style/group/set_default")
async def set_default_style_group(request): async def set_default_style_group(request):
"""设为默认:将某个方案组设为页面初始化的默认配置""" """设为系统初始默认方案"""
try: try:
body = request.json() body = request.json()
group_id = body.get('group_id') group_id = body.get('group_id')
@ -113,7 +121,7 @@ async def set_default_style_group(request):
@app.get("/api/graph/style/groups") @app.get("/api/graph/style/groups")
async def get_group_names(request): async def get_group_names(request):
"""获取所有已存在的方案组列表(用于保存弹窗的下拉选择)""" """获取所有已存在的方案组列表"""
try: try:
data = GraphStyleService.get_group_list() data = GraphStyleService.get_group_list()
return create_response(200, {"code": 200, "data": data, "msg": "查询成功"}) return create_response(200, {"code": 200, "data": data, "msg": "查询成功"})
@ -123,7 +131,7 @@ async def get_group_names(request):
@app.get("/api/graph/style/list") @app.get("/api/graph/style/list")
async def get_style_list(request): async def get_style_list(request):
"""获取原始扁平配置列表(保留兼容性)""" """获取原始扁平配置列表"""
try: try:
data = GraphStyleService.get_all_configs() data = GraphStyleService.get_all_configs()
return create_response(200, {"code": 200, "data": data, "msg": "查询成功"}) return create_response(200, {"code": 200, "data": data, "msg": "查询成功"})

119
service/GraphStyleService.py

@ -7,38 +7,60 @@ class GraphStyleService:
@staticmethod @staticmethod
def save_config(canvas_name: str, current_label: str, styles_dict: dict, group_name: str = None, config_id: int = None) -> bool: 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 # 2. 转换样式 JSON
config_json = json.dumps(styles_dict, ensure_ascii=False) config_json = json.dumps(styles_dict, ensure_ascii=False)
# 3. 【核心修复】:判断是更新(移动)还是新建 # 3. 【核心修改点】:区分 更新 还是 新建
if config_id: if config_id:
# 如果带了 ID,说明是“移动”或“修改”,执行 UPDATE # --- 更新逻辑 ---
# 这样 group_id 会被更新,且不会产生新记录,配置就从原方案“消失”并出现在新方案了 # 如果带了 ID,我们要极其谨慎地处理 group_id,防止在自动保存时被误改
sql = """
UPDATE graph_configs # A. 如果调用者明确传了 group_name,说明是“移动”或“初次保存到某组”
SET canvas_name = %s, current_label = %s, config_json = %s, group_id = %s if group_name and group_name.strip() != "":
WHERE id = %s # 检查/创建 目标方案组
""" check_group_sql = "SELECT id FROM graph_style_groups WHERE group_name = %s LIMIT 1"
affected_rows = mysql_client.execute_update(sql, (canvas_name, current_label, config_json, target_group_id, config_id)) 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: 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 = """ sql = """
INSERT INTO graph_configs (canvas_name, current_label, config_json, group_id) INSERT INTO graph_configs (canvas_name, current_label, config_json, group_id)
VALUES (%s, %s, %s, %s) VALUES (%s, %s, %s, %s)
@ -50,10 +72,8 @@ class GraphStyleService:
@staticmethod @staticmethod
def get_grouped_configs() -> list: def get_grouped_configs() -> list:
""" """
核心优化获取嵌套结构的方案列表 获取嵌套结构的方案列表按默认/激活状态排序
增加 is_active is_default 字段支持并按默认/激活状态排序
""" """
# 1. 查询所有方案组:让默认方案排在最上面
groups_sql = """ groups_sql = """
SELECT id, group_name, is_active, is_default SELECT id, group_name, is_active, is_default
FROM graph_style_groups FROM graph_style_groups
@ -61,11 +81,9 @@ class GraphStyleService:
""" """
groups = mysql_client.execute_query(groups_sql) or [] 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_sql = "SELECT id, group_id, canvas_name, current_label, config_json, create_time FROM graph_configs"
configs = mysql_client.execute_query(configs_sql) or [] configs = mysql_client.execute_query(configs_sql) or []
# 3. 内存聚合
for conf in configs: for conf in configs:
if conf.get('config_json'): if conf.get('config_json'):
try: try:
@ -77,18 +95,12 @@ class GraphStyleService:
if conf.get('create_time') and not isinstance(conf['create_time'], str): 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') conf['create_time'] = conf['create_time'].strftime('%Y-%m-%d %H:%M:%S')
# 组装数据结构
result = [] result = []
for g in groups: for g in groups:
# 兼容 MySQL 布尔值转换 (某些驱动返回 0/1)
g['is_active'] = bool(g['is_active']) g['is_active'] = bool(g['is_active'])
g['is_default'] = bool(g['is_default']) g['is_default'] = bool(g['is_default'])
# 找到属于该组的所有配置
g_children = [c for c in configs if c['group_id'] == g['id']] g_children = [c for c in configs if c['group_id'] == g['id']]
g['configs'] = g_children g['configs'] = g_children
# 如果是激活状态,默认让它在前端展开
g['expanded'] = g['is_active'] g['expanded'] = g['is_active']
result.append(g) result.append(g)
@ -142,16 +154,10 @@ class GraphStyleService:
return [group] # 返回单元素列表,保持接口兼容性 return [group] # 返回单元素列表,保持接口兼容性
@staticmethod @staticmethod
def apply_group_all(group_id: int) -> bool: def apply_group_all(group_id: int) -> bool:
""" """应用全案:设置激活状态"""
核心新增应用全案
逻辑将该组设为 is_active=true其余所有组设为 false
"""
try: try:
# 1. 全部重置为非激活
reset_sql = "UPDATE graph_style_groups SET is_active = %s" reset_sql = "UPDATE graph_style_groups SET is_active = %s"
mysql_client.execute_update(reset_sql, (False,)) mysql_client.execute_update(reset_sql, (False,))
# 2. 激活指定组
apply_sql = "UPDATE graph_style_groups SET is_active = %s WHERE id = %s" apply_sql = "UPDATE graph_style_groups SET is_active = %s WHERE id = %s"
affected_rows = mysql_client.execute_update(apply_sql, (True, group_id)) affected_rows = mysql_client.execute_update(apply_sql, (True, group_id))
return affected_rows > 0 return affected_rows > 0
@ -161,16 +167,10 @@ class GraphStyleService:
@staticmethod @staticmethod
def set_default_group(group_id: int) -> bool: def set_default_group(group_id: int) -> bool:
""" """设为系统初始默认方案"""
核心新增设为系统初始默认方案
逻辑唯一性切换
"""
try: try:
# 1. 全部取消默认
reset_sql = "UPDATE graph_style_groups SET is_default = %s" reset_sql = "UPDATE graph_style_groups SET is_default = %s"
mysql_client.execute_update(reset_sql, (False,)) mysql_client.execute_update(reset_sql, (False,))
# 2. 设置新的默认项
set_sql = "UPDATE graph_style_groups SET is_default = %s WHERE id = %s" set_sql = "UPDATE graph_style_groups SET is_default = %s WHERE id = %s"
affected_rows = mysql_client.execute_update(set_sql, (True, group_id)) affected_rows = mysql_client.execute_update(set_sql, (True, group_id))
return affected_rows > 0 return affected_rows > 0
@ -180,7 +180,7 @@ class GraphStyleService:
@staticmethod @staticmethod
def get_all_configs() -> list: def get_all_configs() -> list:
"""获取扁平查询,增加关联方案的激活状态""" """获取扁平查询"""
sql = """ sql = """
SELECT c.id, c.group_id, c.canvas_name, c.current_label, c.config_json, c.create_time, g.is_active 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 FROM graph_configs c
@ -204,30 +204,27 @@ class GraphStyleService:
@staticmethod @staticmethod
def delete_group(group_id: int) -> bool: def delete_group(group_id: int) -> bool:
"""逻辑级联删除""" """级联删除"""
del_configs_sql = "DELETE FROM graph_configs WHERE group_id = %s" del_configs_sql = "DELETE FROM graph_configs WHERE group_id = %s"
mysql_client.execute_update(del_configs_sql, (group_id,)) mysql_client.execute_update(del_configs_sql, (group_id,))
del_group_sql = "DELETE FROM graph_style_groups WHERE id = %s" del_group_sql = "DELETE FROM graph_style_groups WHERE id = %s"
affected_rows = mysql_client.execute_update(del_group_sql, (group_id,)) affected_rows = mysql_client.execute_update(del_group_sql, (group_id,))
return affected_rows > 0 return affected_rows > 0
@staticmethod @staticmethod
def delete_config(config_id: int) -> bool: def delete_config(config_id: int) -> bool:
"""删除单个配置""" """删除配置"""
sql = "DELETE FROM graph_configs WHERE id = %s" sql = "DELETE FROM graph_configs WHERE id = %s"
affected_rows = mysql_client.execute_update(sql, (config_id,)) affected_rows = mysql_client.execute_update(sql, (config_id,))
return affected_rows > 0 return affected_rows > 0
@staticmethod @staticmethod
def batch_delete_configs(config_ids: list) -> int: def batch_delete_configs(config_ids: list) -> int:
"""批量删除配置""" """批量删除"""
if not config_ids: return 0 if not config_ids: return 0
try: try:
clean_ids = [int(cid) for cid in config_ids if str(cid).isdigit()] clean_ids = [int(cid) for cid in config_ids if str(cid).isdigit()]
except: except: return 0
return 0
if not clean_ids: return 0 if not clean_ids: return 0
placeholders = ', '.join(['%s'] * len(clean_ids)) placeholders = ', '.join(['%s'] * len(clean_ids))
sql = f"DELETE FROM graph_configs WHERE id IN ({placeholders})" sql = f"DELETE FROM graph_configs WHERE id IN ({placeholders})"
@ -235,6 +232,6 @@ class GraphStyleService:
@staticmethod @staticmethod
def get_group_list() -> list: 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 [] return mysql_client.execute_query(sql) or []

653
vue/src/system/GraphStyle.vue

@ -218,8 +218,7 @@
size="small" size="small"
type="info" type="info"
disabled disabled
plain plain>已应用
@click.stop>已应用
</el-button> </el-button>
<i class="el-icon-delete group-del" @click.stop="deleteGroup(group.id)"></i> <i class="el-icon-delete group-del" @click.stop="deleteGroup(group.id)"></i>
@ -236,10 +235,10 @@
<div <div
class="config-card" class="config-card"
:class="{ :class="{
'card-using': usingConfigIds.includes(item.id), 'card-using': item.id === editingConfigId,
'card-checked': checkedConfigIds.includes(item.id) 'card-checked': checkedConfigIds.includes(item.id)
}" }"
@click="toggleApplyConfig(item)" @click="handleEditConfig(item)"
> >
<div class="card-left"> <div class="card-left">
<div class="checkbox-wrapper"> <div class="checkbox-wrapper">
@ -248,7 +247,7 @@
<div class="card-info"> <div class="card-info">
<div class="card-title-row"> <div class="card-title-row">
<span class="card-name">{{ item.canvas_name }}</span> <span class="card-name">{{ item.canvas_name }}</span>
<span v-if="usingConfigIds.includes(item.id)" class="status-badge">已应用</span> <span v-if="item.id === editingConfigId" class="status-badge">编辑中</span>
</div> </div>
<span class="card-tag">标签: {{ item.current_label }}</span> <span class="card-tag">标签: {{ item.current_label }}</span>
</div> </div>
@ -318,7 +317,7 @@
</template> </template>
<script> <script>
import {Graph} from '@antv/g6'; import { Graph } from '@antv/g6';
import Menu from "@/components/Menu.vue"; import Menu from "@/components/Menu.vue";
import { import {
saveGraphStyle, saveGraphStyle,
@ -329,8 +328,8 @@ import {
deleteGraphStyleGroup, deleteGraphStyleGroup,
applyGraphStyleGroup, getGraphStyleActive applyGraphStyleGroup, getGraphStyleActive
} from '@/api/style'; } from '@/api/style';
import {ElMessageBox, ElMessage} from 'element-plus'; import { ElMessageBox, ElMessage } from 'element-plus';
import {markRaw} from 'vue'; import { markRaw } from 'vue';
const tagToLabelMap = { const tagToLabelMap = {
'疾病': 'Disease', '症状': 'Symptom', '病因': 'Cause', '药品': 'Drug', '科室': 'Department', '检查': 'Check', '其他': 'Other' '疾病': 'Disease', '症状': 'Symptom', '病因': 'Cause', '药品': 'Drug', '科室': 'Department', '检查': 'Check', '其他': 'Other'
@ -349,7 +348,7 @@ const INITIAL_STROKE_MAP = {
export default { export default {
name: 'GraphDemo', name: 'GraphDemo',
components: {Menu}, components: { Menu },
data() { data() {
return { return {
activeTags: '疾病', activeTags: '疾病',
@ -359,8 +358,10 @@ export default {
checkedConfigIds: [], checkedConfigIds: [],
checkedGroupIds: [], checkedGroupIds: [],
usingConfigIds: [], usingConfigIds: [],
editingConfigId: null,
editingConfigLabel: '',
saveDialogVisible: false, saveDialogVisible: false,
saveForm: {group_name: '', canvas_name: ''}, saveForm: { group_name: '', canvas_name: '' },
tagStyles: { tagStyles: {
'Disease': this.getInitialTagParams('Disease'), 'Disease': this.getInitialTagParams('Disease'),
'Symptom': this.getInitialTagParams('Symptom'), 'Symptom': this.getInitialTagParams('Symptom'),
@ -376,7 +377,7 @@ export default {
nodeFontSize: 12, nodeFontSize: 12,
nodeFontColor: '#ffffff', nodeFontColor: '#ffffff',
nodeShape: 'circle', nodeShape: 'circle',
nodeSize: 50, nodeSize: 60,
nodeFill: '#EF4444', nodeFill: '#EF4444',
nodeStroke: '#B91C1C', nodeStroke: '#B91C1C',
nodeLineWidth: 2, nodeLineWidth: 2,
@ -390,31 +391,33 @@ export default {
edgeStroke: '#EF4444', edgeStroke: '#EF4444',
defaultData: { defaultData: {
nodes: [ nodes: [
{id: "node1", data: {name: "霍乱", label: "Disease"}}, { id: "node1", data: { name: "霍乱", label: "Disease" } },
{id: "node2", data: {name: "腹泻", label: "Symptom"}}, { id: "node2", data: { name: "腹泻", label: "Symptom" } },
{id: "node3", data: {name: "脱水", label: "Disease"}}, { id: "node3", data: { name: "脱水", label: "Disease" } },
{id: "node4", data: {name: "呕吐", label: "Disease"}}, { id: "node4", data: { name: "呕吐", label: "Disease" } },
{id: "node5", data: {name: "由霍乱弧菌感染所致", label: "Cause"}}, { id: "node5", data: { name: "由霍乱弧菌感染所致", label: "Cause" } },
{id: "node6", data: {name: "复方磺胺甲噁唑", label: "Drug"}}, { id: "node6", data: { name: "复方磺胺甲噁唑", label: "Drug" } },
{id: "node7", data: {name: "消化系统", label: "DiseaseSite"}}, { id: "node7", data: { name: "消化系统", label: "DiseaseSite" } },
{id: "node8", data: {name: "传染科", label: "Department"}}, { id: "node8", data: { name: "传染科", label: "Department" } },
{id: "node9", data: {name: "代谢性 酸中毒", label: "Disease"}}, { id: "node9", data: { name: "代谢性 酸中毒", label: "Disease" } },
{id: "node10", data: {name: "急性肾衰竭", label: "Disease"}}, { id: "node10", data: { name: "急性肾衰竭", label: "Disease" } },
{id: "node11", data: {name: "检查项目", label: "Check"}} { id: "node11", data: { name: "检查项目", label: "Check" } }
], ],
edges: [ edges: [
{id: "e1", source: "node1", target: "node2", data: {relationship: {properties: {label: "症状与体征"}}}}, { id: "e1", source: "node1", target: "node2", data: { relationship: { properties: { label: "症状与体征" } } } },
{id: "e2", source: "node1", target: "node3", data: {relationship: {properties: {label: "并发症"}}}}, { id: "e2", source: "node1", target: "node3", data: { relationship: { properties: { label: "并发症" } } } },
{id: "e3", source: "node1", target: "node4", data: {relationship: {properties: {label: "并发症"}}}}, { id: "e3", source: "node1", target: "node4", data: { relationship: { properties: { label: "并发症" } } } },
{id: "e4", source: "node1", target: "node5", data: {relationship: {properties: {label: "病因"}}}}, { id: "e4", source: "node1", target: "node5", data: { relationship: { properties: { label: "病因" } } } },
{id: "e5", source: "node1", target: "node6", data: {relationship: {properties: {label: "治疗方案"}}}}, { id: "e5", source: "node1", target: "node6", data: { relationship: { properties: { label: "治疗方案" } } } },
{id: "e6", source: "node1", target: "node7", data: {relationship: {properties: {label: "病变部位"}}}}, { id: "e6", source: "node1", target: "node7", data: { relationship: { properties: { label: "病变部位" } } } },
{id: "e7", source: "node1", target: "node8", data: {relationship: {properties: {label: "科室"}}}}, { id: "e7", source: "node1", target: "node8", data: { relationship: { properties: { label: "科室" } } } },
{id: "e8", source: "node1", target: "node9", data: {relationship: {properties: {label: "并发症"}}}}, { id: "e8", source: "node1", target: "node9", data: { relationship: { properties: { label: "并发症" } } } },
{id: "e9", source: "node1", target: "node10", data: {relationship: {properties: {label: "并发症"}}}}, { id: "e9", source: "node1", target: "node10", data: { relationship: { properties: { label: "并发症" } } } },
{id: "e10", source: "node1", target: "node11", data: {relationship: {properties: {label: "检查"}}}} { id: "e10", source: "node1", target: "node11", data: { relationship: { properties: { label: "检查" } } } }
] ]
} },
saveTimer: null,
isInitialEcho: false
} }
}, },
watch: { watch: {
@ -459,40 +462,141 @@ export default {
this._graph = null; this._graph = null;
} }
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
if (this.saveTimer) clearTimeout(this.saveTimer);
}, },
methods: { methods: {
handleEditConfig(item) {
if (this.saveTimer) clearTimeout(this.saveTimer);
this.isInitialEcho = true;
this.editingConfigId = item.id;
this.editingConfigLabel = item.current_label;
this.activeTags = item.current_label;
const s = item.styles;
if (!s) {
this.isInitialEcho = false;
return;
}
this.nodeShowLabel = s.nodeShowLabel;
this.nodeFontFamily = s.nodeFontFamily;
this.nodeFontSize = s.nodeFontSize;
this.nodeFontColor = s.nodeFontColor;
this.nodeShape = s.nodeShape;
this.nodeSize = s.nodeSize;
this.nodeFill = s.nodeFill;
this.nodeStroke = s.nodeStroke;
this.nodeLineWidth = s.nodeLineWidth;
this.edgeShowLabel = s.edgeShowLabel;
this.edgeEndArrow = s.edgeEndArrow;
this.edgeFontFamily = s.edgeFontFamily;
this.edgeFontSize = s.edgeFontSize;
this.edgeFontColor = s.edgeFontColor;
this.edgeType = s.edgeType;
this.edgeLineWidth = s.edgeLineWidth;
this.edgeStroke = s.edgeStroke;
const labelEn = tagToLabelMap[item.current_label];
if (labelEn) this.tagStyles[labelEn] = JSON.parse(JSON.stringify(s));
if (!this.usingConfigIds.includes(item.id)) {
this.styleGroups.forEach(g => {
g.configs.forEach(c => {
if (this.usingConfigIds.includes(c.id) && c.current_label === item.current_label) {
this.usingConfigIds = this.usingConfigIds.filter(id => id !== c.id);
}
});
});
this.usingConfigIds.push(item.id);
}
this.updateAllElements();
this.$nextTick(() => {
setTimeout(() => {
this.isInitialEcho = false;
}, 100);
});
},
syncAndRefresh() {
if (this.isInitialEcho) return;
const labelEn = tagToLabelMap[this.activeTags];
if (!labelEn) return;
const currentStyle = {
nodeShowLabel: this.nodeShowLabel, nodeFontFamily: this.nodeFontFamily, nodeFontSize: this.nodeFontSize,
nodeFontColor: this.nodeFontColor, nodeShape: this.nodeShape, nodeSize: this.nodeSize,
nodeFill: this.nodeFill, nodeStroke: this.nodeStroke, nodeLineWidth: this.nodeLineWidth,
edgeShowLabel: this.edgeShowLabel, edgeEndArrow: this.edgeEndArrow, edgeFontFamily: this.edgeFontFamily,
edgeFontSize: this.edgeFontSize, edgeFontColor: this.edgeFontColor, edgeType: this.edgeType,
edgeLineWidth: this.edgeLineWidth, edgeStroke: this.edgeStroke
};
this.tagStyles[labelEn] = currentStyle;
this.updateAllElements();
if (this.editingConfigId) {
if (this.saveTimer) clearTimeout(this.saveTimer);
const currentEditId = this.editingConfigId;
this.saveTimer = setTimeout(async () => {
try {
let targetConf = null;
let groupName = null;
for (const group of this.styleGroups) {
targetConf = group.configs.find(c => c.id === currentEditId);
if (targetConf) {
groupName = group.group_name;
break;
}
}
if (targetConf) {
const payload = {
id: currentEditId,
canvas_name: targetConf.canvas_name,
current_label: targetConf.current_label,
group_name: groupName,
styles: currentStyle,
is_auto_save: true
};
await saveGraphStyle(payload);
targetConf.styles = JSON.parse(JSON.stringify(currentStyle));
}
} catch (err) {
console.error("同步失败:", err);
}
}, 800);
}
},
validateNodeSize(event) { validateNodeSize(event) {
const inputVal = event.target.value; const val = parseInt(event.target.value);
const val = parseInt(inputVal);
if (isNaN(val) || val < 30 || val > 100) { if (isNaN(val) || val < 30 || val > 100) {
ElMessage({ message: `节点尺寸请输入 30 到 100 之间的数字`, type: 'warning', duration: 1500 }); ElMessage({ message: `请输入 30 到 100`, type: 'warning' });
event.target.value = this.nodeSize; event.target.value = this.nodeSize;
return; return;
} }
this.nodeSize = val; this.nodeSize = val;
this.syncAndRefresh();
}, },
validateNodeLineWidth(event) { validateNodeLineWidth(event) {
const inputVal = event.target.value; const val = parseInt(event.target.value);
const val = parseInt(inputVal);
if (isNaN(val) || val < 1 || val > 5) { if (isNaN(val) || val < 1 || val > 5) {
ElMessage({ message: `边框尺寸请输入 1 到 5 之间的数字`, type: 'warning', duration: 1500 }); ElMessage({ message: `请输入 1 到 5`, type: 'warning' });
event.target.value = this.nodeLineWidth; event.target.value = this.nodeLineWidth;
return; return;
} }
this.nodeLineWidth = val; this.nodeLineWidth = val;
this.syncAndRefresh();
}, },
validateEdgeLineWidth(event) { validateEdgeLineWidth(event) {
const inputVal = event.target.value; const val = parseInt(event.target.value);
const val = parseInt(inputVal);
if (isNaN(val) || val < 1 || val > 5) { if (isNaN(val) || val < 1 || val > 5) {
ElMessage({ message: `线条粗细请输入 1 到 5 之间的数字`, type: 'warning', duration: 1500 }); ElMessage({ message: `请输入 1 到 5`, type: 'warning' });
event.target.value = this.edgeLineWidth; event.target.value = this.edgeLineWidth;
return; return;
} }
this.edgeLineWidth = val; this.edgeLineWidth = val;
this.syncAndRefresh();
}, },
getInitialTagParams(label) { getInitialTagParams(label) {
const fill = INITIAL_FILL_MAP[label] || '#59d1d4'; const fill = INITIAL_FILL_MAP[label] || '#59d1d4';
@ -504,27 +608,35 @@ export default {
edgeLineWidth: 2, edgeStroke: fill edgeLineWidth: 2, edgeStroke: fill
}; };
}, },
handleTagClick(tag) { handleTagClick(tag) {
this.activeTags = tag; if (this.editingConfigId && this.editingConfigLabel !== tag) {
const label = tagToLabelMap[tag]; ElMessageBox.confirm(
const style = this.tagStyles[label]; `当前正在编辑【${this.editingConfigLabel}】的存储配置,切换到【${tag}】将退出编辑模式,未保存的修改可能丢失。`,
if (style) Object.assign(this, style); '提示',
{ confirmButtonText: '确定切换', cancelButtonText: '取消', type: 'warning' }
).then(() => {
this.editingConfigId = null;
this.editingConfigLabel = '';
this.performTagSwitch(tag);
}).catch(() => {});
} else {
this.performTagSwitch(tag);
}
}, },
syncAndRefresh() {
const label = tagToLabelMap[this.activeTags]; performTagSwitch(tag) {
console.log(this.nodeFill) this.activeTags = tag;
if (label) { const labelEn = tagToLabelMap[tag];
this.tagStyles[label] = { const style = this.tagStyles[labelEn];
nodeShowLabel: this.nodeShowLabel, nodeFontFamily: this.nodeFontFamily, nodeFontSize: this.nodeFontSize, if (style) {
nodeFontColor: this.nodeFontColor, nodeShape: this.nodeShape, nodeSize: this.nodeSize, this.isInitialEcho = true;
nodeFill: this.nodeFill, nodeStroke: this.nodeStroke, nodeLineWidth: this.nodeLineWidth, Object.assign(this, style);
edgeShowLabel: this.edgeShowLabel, edgeEndArrow: this.edgeEndArrow, edgeFontFamily: this.edgeFontFamily, this.$nextTick(() => { this.isInitialEcho = false; });
edgeFontSize: this.edgeFontSize, edgeFontColor: this.edgeFontColor, edgeType: this.edgeType,
edgeLineWidth: this.edgeLineWidth, edgeStroke: this.edgeStroke
};
} }
this.updateAllElements(); this.updateAllElements();
}, },
initGraph() { initGraph() {
const container = this.$refs.graphContainer; const container = this.$refs.graphContainer;
if (!container || container.clientWidth === 0) return; if (!container || container.clientWidth === 0) return;
@ -532,9 +644,9 @@ export default {
if (this._graph) this._graph.destroy(); if (this._graph) this._graph.destroy();
const graph = new Graph({ const graph = new Graph({
container, width: container.clientWidth, height: container.clientHeight || 600, container, width: container.clientWidth, height: container.clientHeight || 600,
layout: {type: 'radial', unitRadius: 100, preventOverlap: true, nodeSpacing: 50}, layout: { type: 'radial', unitRadius: 100, preventOverlap: true, nodeSpacing: 50 },
behaviors: ['zoom-canvas', 'drag-canvas', 'drag-element', 'hover-activate'], behaviors: ['zoom-canvas', 'drag-canvas', 'drag-element', 'hover-activate'],
autoFit: 'center', animation: true autoFit: 'center', animation: false
}); });
this._graph = markRaw(graph); this._graph = markRaw(graph);
this.updateAllElements(); this.updateAllElements();
@ -545,6 +657,7 @@ export default {
updateAllElements() { updateAllElements() {
if (!this._graph) return; if (!this._graph) return;
const currentActiveLabelEn = tagToLabelMap[this.activeTags];
const labelToAppliedConfigMap = {}; const labelToAppliedConfigMap = {};
this.styleGroups.forEach(group => { this.styleGroups.forEach(group => {
group.configs.forEach(conf => { group.configs.forEach(conf => {
@ -565,7 +678,18 @@ export default {
const nodes = this.defaultData.nodes.map(node => { const nodes = this.defaultData.nodes.map(node => {
const rawLabel = node.data?.label || ''; const rawLabel = node.data?.label || '';
const effectiveKey = this.getEffectiveStyleKey(rawLabel); const effectiveKey = this.getEffectiveStyleKey(rawLabel);
const s = labelToAppliedConfigMap[effectiveKey] || this.tagStyles[effectiveKey];
let s;
if (effectiveKey === currentActiveLabelEn) {
s = {
nodeShape: this.nodeShape, nodeSize: this.nodeSize, nodeFill: this.nodeFill,
nodeStroke: this.nodeStroke, nodeLineWidth: this.nodeLineWidth,
nodeShowLabel: this.nodeShowLabel, nodeFontColor: this.nodeFontColor,
nodeFontSize: this.nodeFontSize, nodeFontFamily: this.nodeFontFamily
};
} else {
s = labelToAppliedConfigMap[effectiveKey] || this.tagStyles[effectiveKey];
}
return { return {
...node, type: s?.nodeShape || 'circle', ...node, type: s?.nodeShape || 'circle',
@ -582,27 +706,31 @@ export default {
const edges = this.defaultData.edges.map(edge => { const edges = this.defaultData.edges.map(edge => {
const sRawLabel = this._nodeLabelMap.get(edge.source); const sRawLabel = this._nodeLabelMap.get(edge.source);
const effectiveKey = this.getEffectiveStyleKey(sRawLabel); const effectiveKey = this.getEffectiveStyleKey(sRawLabel);
const s = labelToAppliedConfigMap[effectiveKey] || this.tagStyles[effectiveKey] || this;
const strokeColor = hexToRgba(s.edgeStroke, 0.6); let s;
if (effectiveKey === currentActiveLabelEn) {
s = {
edgeType: this.edgeType, edgeStroke: this.edgeStroke, edgeLineWidth: this.edgeLineWidth,
edgeEndArrow: this.edgeEndArrow, edgeShowLabel: this.edgeShowLabel,
edgeFontColor: this.edgeFontColor, edgeFontSize: this.edgeFontSize, edgeFontFamily: this.edgeFontFamily
};
} else {
s = labelToAppliedConfigMap[effectiveKey] || this.tagStyles[effectiveKey];
}
const strokeColor = hexToRgba(s?.edgeStroke || '#EF4444', 0.6);
return { return {
...edge, type: s.edgeType || 'line', ...edge, type: s?.edgeType || 'line',
style: { style: {
stroke: strokeColor, stroke: strokeColor, lineWidth: this.safeNum(s?.edgeLineWidth, 2), endArrow: s?.edgeEndArrow,
lineWidth: this.safeNum(s.edgeLineWidth, 2), labelText: s?.edgeShowLabel ? (edge.data?.relationship?.properties?.label || '') : '',
endArrow: s.edgeEndArrow, labelFill: s?.edgeFontColor || '#666', labelFontSize: this.safeNum(s?.edgeFontSize, 10),
labelText: s.edgeShowLabel ? (edge.data?.relationship?.properties?.label || '') : '', labelFontFamily: s?.edgeFontFamily || 'Microsoft YaHei', labelBackground: true,
labelFill: s.edgeFontColor || '#666', labelBackgroundFill: '#fff', labelBackgroundOpacity: 0.7
labelFontSize: this.safeNum(s.edgeFontSize, 10),
labelFontFamily: s.edgeFontFamily || 'Microsoft YaHei',
labelBackground: true,
labelBackgroundFill: '#fff',
labelBackgroundOpacity: 0.7
} }
}; };
}); });
this._graph.setData({ nodes, edges });
this._graph.setData({nodes, edges});
this._graph.render(); this._graph.render();
}, },
safeNum(val, defaultVal = 1) { safeNum(val, defaultVal = 1) {
@ -613,66 +741,37 @@ export default {
try { try {
const res = await getGroupedGraphStyleList(); const res = await getGroupedGraphStyleList();
if (res.code === 200) { if (res.code === 200) {
// 1.
// ID
this.styleGroups = res.data.map(group => { this.styleGroups = res.data.map(group => {
const idSet = new Set(); const uniqueConfigs = (group.configs || []).map(conf => ({
const uniqueConfigs = []; ...conf,
styles: typeof conf.styles === 'string' ? JSON.parse(conf.styles) : conf.styles
(group.configs || []).forEach(conf => { }));
if (!idSet.has(conf.id)) { return { ...group, configs: uniqueConfigs };
idSet.add(conf.id);
uniqueConfigs.push({
...conf,
// styles
styles: typeof conf.styles === 'string' ? JSON.parse(conf.styles) : conf.styles
});
} else {
console.warn(`检测到重复 ID: ${conf.id},已在前端过滤`);
}
});
return {
...group,
configs: uniqueConfigs
};
}); });
// 2. const activeGroup = this.styleGroups.find(g => g.is_active);
if (this.usingConfigIds.length === 0) { if (activeGroup) {
const tempUsingIds = []; this.usingConfigIds = activeGroup.configs.map(c => c.id);
const seenLabels = new Set(); // activeGroup.configs.forEach(conf => {
const labelEn = tagToLabelMap[conf.current_label];
this.styleGroups.forEach(group => { if (labelEn) {
// active this.tagStyles[labelEn] = JSON.parse(JSON.stringify(conf.styles));
if (group.is_active) {
//
if (!this.activeCollapseNames.includes(group.id)) {
this.activeCollapseNames.push(group.id);
}
//
group.configs.forEach(conf => {
if (!seenLabels.has(conf.current_label)) {
tempUsingIds.push(conf.id);
seenLabels.add(conf.current_label);
} else {
console.warn(`标签冲突拦截:方案【${group.group_name}】中的配置【${conf.canvas_name}】因标签【${conf.current_label}】已存在而被忽略`);
}
});
} }
}); });
// const currentActiveConf = activeGroup.configs.find(c => c.current_label === this.activeTags);
this.usingConfigIds = tempUsingIds; if (currentActiveConf) {
this.isInitialEcho = true;
Object.assign(this, currentActiveConf.styles);
this.editingConfigId = currentActiveConf.id;
this.editingConfigLabel = currentActiveConf.current_label;
this.$nextTick(() => { this.isInitialEcho = false; });
}
} }
// 3.
this.updateAllElements(); this.updateAllElements();
} }
} catch (err) { } catch (err) {
console.error("加载配置失败:", err); console.error("加载配置失败:", err);
ElMessage.error("获取方案列表失败");
} }
}, },
async fetchGroupNames() { async fetchGroupNames() {
@ -683,117 +782,68 @@ export default {
const idx = this.usingConfigIds.indexOf(item.id); const idx = this.usingConfigIds.indexOf(item.id);
if (idx > -1) { if (idx > -1) {
this.usingConfigIds.splice(idx, 1); this.usingConfigIds.splice(idx, 1);
if (this.editingConfigId === item.id) {
this.editingConfigId = null;
this.editingConfigLabel = '';
}
} else { } else {
// this.handleEditConfig(item);
this.styleGroups.forEach(g => {
g.configs.forEach(c => {
if (this.usingConfigIds.includes(c.id) && c.current_label === item.current_label) {
this.usingConfigIds = this.usingConfigIds.filter(id => id !== c.id);
}
});
});
this.usingConfigIds.push(item.id);
} }
this.updateAllElements(); this.updateAllElements();
}, },
/**
* 新增功能将配置移动到另一个方案 // validateGroupConstraint
*/ validateGroupConstraint(groupName, labelName, excludeId = null) {
const group = this.styleGroups.find(g => g.group_name === groupName);
if (!group) return true;
// 1.
const isLabelExist = group.configs.some(c => c.current_label === labelName && c.id !== excludeId);
if (isLabelExist) {
// 使 alert confirm .catch()
ElMessageBox.alert(
`方案【${groupName}】中已存在【${labelName}】标签的配置,请先删除旧配置或选择其他方案。`,
'校验失败',
{ type: 'error' }
).catch(() => {}); //
return false;
}
// 2. 5
if (group.configs.length >= 5 && !group.configs.some(c => c.id === excludeId)) {
ElMessageBox.alert(
`方案【${groupName}】的配置已满(上限5个),无法添加。`,
'校验失败',
{ type: 'error' }
).catch(() => {});
return false;
}
return true;
},
async moveConfigToGroup(config, targetGroup) { async moveConfigToGroup(config, targetGroup) {
//
if (!this.validateGroupConstraint(targetGroup.group_name, config.current_label, config.id)) {
return;
}
try { try {
// 1.
const payload = { const payload = {
id: config.id, // ID id: config.id,
canvas_name: config.canvas_name, canvas_name: config.canvas_name,
group_name: targetGroup.group_name, // group_name: targetGroup.group_name,
current_label: config.current_label, current_label: config.current_label,
styles: config.styles styles: config.styles,
is_auto_save: false
}; };
const res = await saveGraphStyle(payload); const res = await saveGraphStyle(payload);
if (res.code === 200) { if (res.code === 200) {
ElMessage.success(`已成功移动至【${targetGroup.group_name}`); ElMessage.success(`已移动至【${targetGroup.group_name}`);
// 2. styleGroups fetchConfigs
this.styleGroups = this.styleGroups.map(group => {
return {
...group,
// ID ID
configs: group.configs.filter(c => c.id !== config.id)
};
});
// 3.
await this.fetchConfigs(); await this.fetchConfigs();
} }
} catch (err) { } catch (err) {
console.error("移动配置失败:", err); ElMessage.error("操作失败");
ElMessage.error("移动操作失败,请重试");
}
},
/**
* 修改后的应用全案方法
* 逻辑保留当前已手动选中的配置新方案中冲突的配置不予应用
*/
async applyWholeGroup(group) {
try {
// 1. 5
const REQUIRED_TAGS = ['疾病', '症状', '药品', '检查', '其他'];
// 2.
const currentLabels = group.configs.map(conf => conf.current_label);
const hasTags = new Set(currentLabels);
// 3.
const missingTags = REQUIRED_TAGS.filter(tag => !hasTags.has(tag));
// 4.
if (missingTags.length > 0) {
return ElMessage.warning('该方案配置不完整,无法应用。目前缺少:'+missingTags.join('、'));
}
// 使
const currentlyUsingLabels = [];
this.styleGroups.forEach(g => {
g.configs.forEach(c => {
if (this.usingConfigIds.includes(c.id)) {
currentlyUsingLabels.push(c.current_label);
}
});
});
//
const uniqueNewConfigs = [];
const seenLabelsInNewGroup = new Set();
group.configs.forEach(conf => {
if (!seenLabelsInNewGroup.has(conf.current_label)) {
uniqueNewConfigs.push(conf);
seenLabelsInNewGroup.add(conf.current_label);
}
});
//
const filteredNewConfigIds = uniqueNewConfigs
.filter(newConf => !currentlyUsingLabels.includes(newConf.current_label))
.map(newConf => newConf.id);
if (filteredNewConfigIds.length === 0) {
return ElMessage.info("该方案中的标签配置已存在,无需重复应用");
}
// ID
this.usingConfigIds = [...this.usingConfigIds, ...filteredNewConfigIds];
//
const res = await applyGraphStyleGroup(group.id);
if (res.code === 200) {
await this.fetchConfigs();
ElMessage.success(`方案【${group.group_name}】已应用,已自动过滤重复标签`);
}
} catch (err) {
console.error(err);
ElMessage.error("应用全案失败");
} }
}, },
@ -802,142 +852,117 @@ export default {
this.saveForm.canvas_name = `${this.activeTags}_${Date.now()}`; this.saveForm.canvas_name = `${this.activeTags}_${Date.now()}`;
this.saveDialogVisible = true; this.saveDialogVisible = true;
}, },
async confirmSave() { async confirmSave() {
if (!this.saveForm.group_name || !this.saveForm.group_name.trim()) { if (!this.saveForm.group_name?.trim() || !this.saveForm.canvas_name?.trim()) {
return ElMessage.warning("请选择或输入方案名称"); return ElMessage.warning("请完善名称");
} }
if (!this.saveForm.canvas_name || !this.saveForm.canvas_name.trim()) {
return ElMessage.warning("请输入配置名称"); //
if (!this.validateGroupConstraint(this.saveForm.group_name.trim(), this.activeTags)) {
return;
} }
const labelEn = tagToLabelMap[this.activeTags];
const payload = { const payload = {
canvas_name: this.saveForm.canvas_name.trim(), canvas_name: this.saveForm.canvas_name.trim(),
group_name: this.saveForm.group_name.trim(), group_name: this.saveForm.group_name.trim(),
current_label: this.activeTags, current_label: this.activeTags,
styles: {...this.tagStyles[tagToLabelMap[this.activeTags]]} styles: { ...this.tagStyles[labelEn] },
is_auto_save: false
}; };
const res = await saveGraphStyle(payload); const res = await saveGraphStyle(payload);
if (res.code === 200) { if (res.code === 200) {
ElMessage.success("保存成功"); ElMessage.success("保存成功");
this.saveDialogVisible = false; this.saveDialogVisible = false;
this.saveForm.canvas_name = ''; await this.fetchConfigs();
this.saveForm.group_name = '';
this.resetAllTagsToDefault();
this.fetchConfigs();
} }
}, },
resetAllTagsToDefault() { // ===========================================================================
Object.keys(this.tagStyles).forEach(labelKey => {
this.tagStyles[labelKey] = this.getInitialTagParams(labelKey); async applyWholeGroup(group) {
}); if (this.saveTimer) clearTimeout(this.saveTimer);
this.activeTags = '疾病'; this.isInitialEcho = true;
const diseaseInitial = this.getInitialTagParams('Disease'); this.editingConfigId = null;
Object.assign(this, diseaseInitial); this.editingConfigLabel = '';
this.updateAllElements();
try {
const REQUIRED_TAGS = ['疾病', '症状', '药品', '检查', '其他'];
const currentLabels = group.configs.map(conf => conf.current_label);
const hasTags = new Set(currentLabels);
const missingTags = REQUIRED_TAGS.filter(tag => !hasTags.has(tag));
if (missingTags.length > 0) {
this.isInitialEcho = false;
return ElMessageBox.alert(
`该方案配置不完整,无法应用。<br/>缺失:<b style="color: #f56c6c">${missingTags.join('、')}</b>`,
'提示', { dangerouslyUseHTMLString: true, type: 'warning' }
);
}
this.usingConfigIds = group.configs.map(c => c.id);
const res = await applyGraphStyleGroup(group.id);
if (res.code === 200) {
await this.fetchConfigs();
if (group.configs.length > 0) {
this.handleEditConfig(group.configs[0]);
}
ElMessage.success(`已应用方案【${group.group_name}`);
}
} catch (err) {
this.isInitialEcho = false;
ElMessage.error("切换失败");
}
}, },
resetStyle() { resetStyle() {
const labelEn = tagToLabelMap[this.activeTags]; const labelEn = tagToLabelMap[this.activeTags];
const initial = this.getInitialTagParams(labelEn); const initial = this.getInitialTagParams(labelEn);
this.tagStyles[labelEn] = initial; this.tagStyles[labelEn] = initial;
this.isInitialEcho = true;
Object.assign(this, initial); Object.assign(this, initial);
this.updateAllElements(); this.$nextTick(() => {
ElMessage.info(`已重置【${this.activeTags}】样式`); this.isInitialEcho = false;
this.syncAndRefresh();
});
}, },
// --- ---
async deleteSingleConfig(id) { async deleteSingleConfig(id) {
if (this.usingConfigIds.includes(id)) { if (this.usingConfigIds.includes(id)) return ElMessage.error("应用中无法删除");
return ElMessage.error("该配置正在应用中,请取消应用或切换方案后再删除");
}
try { try {
await ElMessageBox.confirm('确定删除此配置吗?', '提示'); await ElMessageBox.confirm('确定删除吗?', '提示');
const res = await deleteGraphStyle(id); const res = await deleteGraphStyle(id);
if (res.code === 200) { if (res.code === 200) {
ElMessage.success("删除成功"); ElMessage.success("删除成功");
this.fetchConfigs(); this.fetchConfigs();
} }
} catch (err) {} } catch (err) { }
}, },
// --- ---
async deleteGroup(groupId) { async deleteGroup(groupId) {
const group = this.styleGroups.find(g => g.id === groupId); const group = this.styleGroups.find(g => g.id === groupId);
if (!group) return; if (!group || group.is_active) return ElMessage.error("应用中无法删除");
// 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 { try {
await ElMessageBox.confirm('确定删除整个方案吗?', '提示'); await ElMessageBox.confirm('确定删除全案吗?', '提示');
const res = await deleteGraphStyleGroup(groupId); const res = await deleteGraphStyleGroup(groupId);
if (res.code === 200) { if (res.code === 200) {
ElMessage.success("方案已删除"); ElMessage.success("已删除");
this.fetchConfigs(); this.fetchConfigs();
} }
} catch (err) {} } catch (err) { }
}, },
// --- ---
async handleUnifiedBatchDelete() { async handleUnifiedBatchDelete() {
// 1. if (this.checkedConfigIds.length === 0 && this.checkedGroupIds.length === 0) return;
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 ElMessage.warning('选中的项目中包含“正在应用”的配置,请先取消应用后再执行删除操作。');
}
// 3. ()
if (this.checkedGroupIds.length >= this.styleGroups.length && this.styleGroups.length > 0) {
return ElMessage.error("系统至少需要保留一个方案,请勿全部勾选删除");
}
try { try {
await ElMessageBox.confirm( await ElMessageBox.confirm('确定批量删除吗?', '提示');
'确定执行批量删除吗?此操作不可恢复。',
'批量删除确认',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning'
}
);
//
if (this.checkedGroupIds.length > 0) { if (this.checkedGroupIds.length > 0) {
for (const gid of this.checkedGroupIds) { for (const gid of this.checkedGroupIds) await deleteGraphStyleGroup(gid);
await deleteGraphStyleGroup(gid);
}
} }
if (this.checkedConfigIds.length > 0) { if (this.checkedConfigIds.length > 0) {
await batchDeleteGraphStyle({ids: this.checkedConfigIds}); await batchDeleteGraphStyle({ ids: this.checkedConfigIds });
} }
ElMessage.success("成功");
ElMessage.success("批量删除成功");
this.clearSelection(); this.clearSelection();
this.fetchConfigs(); this.fetchConfigs();
this.updateAllElements(); } catch (e) { }
} catch (e) {
console.log("用户取消或删除失败", e);
}
}, },
clearSelection() { clearSelection() {
this.checkedConfigIds = []; this.checkedConfigIds = [];
this.checkedGroupIds = []; this.checkedGroupIds = [];

Loading…
Cancel
Save