|
|
@@ -59,6 +59,46 @@ def calculate_intent_support_count(element: Dict[str, Any]) -> int:
|
|
|
return total_support_count
|
|
|
|
|
|
|
|
|
+def get_element_coverage(element: Dict[str, Any]) -> float:
|
|
|
+ """
|
|
|
+ 获取元素的段落覆盖率(兼容形式元素和实质元素)
|
|
|
+
|
|
|
+ 对于实质元素:从"共性分析"字段获取
|
|
|
+ 对于形式元素:从"权重明细"中的"覆盖率分"反推
|
|
|
+ 根据script_form_extraction_agent.py的逻辑:
|
|
|
+ - coverage_rate_base_score = coverage_rate * 50 (基础分0-50)
|
|
|
+ - coverage_rate_score = coverage_rate_base_score * 0.3 (加权后0-15)
|
|
|
+ 所以:coverage_rate = coverage_rate_score / 15
|
|
|
+
|
|
|
+ Args:
|
|
|
+ element: 元素数据
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ 段落覆盖率(0.0-1.0)
|
|
|
+ """
|
|
|
+ dimension = element.get('维度', {})
|
|
|
+ is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式'
|
|
|
+
|
|
|
+ if is_form:
|
|
|
+ # 形式元素:从权重明细中反推覆盖率
|
|
|
+ weight_details = element.get('权重明细', {})
|
|
|
+ if weight_details:
|
|
|
+ coverage_score = float(weight_details.get('覆盖率分', 0) or 0)
|
|
|
+ # 覆盖率分 = 覆盖率 × 50 × 0.3 = 覆盖率 × 15
|
|
|
+ # 所以:覆盖率 = 覆盖率分 / 15
|
|
|
+ if coverage_score > 0:
|
|
|
+ coverage = coverage_score / 15.0
|
|
|
+ return min(1.0, max(0.0, coverage))
|
|
|
+
|
|
|
+ # 如果权重明细中没有覆盖率分,返回0
|
|
|
+ return 0.0
|
|
|
+ else:
|
|
|
+ # 实质元素:从共性分析中获取
|
|
|
+ commonality = element.get('共性分析') or {}
|
|
|
+ coverage = float(commonality.get('段落覆盖率', 0.0) or 0.0)
|
|
|
+ return coverage
|
|
|
+
|
|
|
+
|
|
|
def get_support_stats(element: Dict[str, Any]) -> Dict[str, int]:
|
|
|
"""
|
|
|
获取元素的支撑统计信息(灵感点/目的点/关键点数量)
|
|
|
@@ -379,7 +419,7 @@ def group_elements_by_hierarchical_category(elements: List[Dict[str, Any]]) -> D
|
|
|
continue
|
|
|
|
|
|
# 计算统计指标
|
|
|
- avg_coverage = sum((e.get('共性分析') or {}).get('段落覆盖率', 0.0) for e in all_elements) / len(all_elements)
|
|
|
+ avg_coverage = sum(get_element_coverage(e) for e in all_elements) / len(all_elements)
|
|
|
avg_frequency = sum((e.get('共性分析') or {}).get('出现频次', 0) for e in all_elements) / len(all_elements)
|
|
|
avg_intent_count = sum(calculate_intent_support_count(e) for e in all_elements) / len(all_elements)
|
|
|
|
|
|
@@ -406,7 +446,7 @@ def group_elements_by_hierarchical_category(elements: List[Dict[str, Any]]) -> D
|
|
|
level2_scores[level2_name] = (0.0, 0, 0.0)
|
|
|
continue
|
|
|
|
|
|
- avg_coverage = sum((e.get('共性分析') or {}).get('段落覆盖率', 0.0) for e in level2_elements) / len(level2_elements)
|
|
|
+ avg_coverage = sum(get_element_coverage(e) for e in level2_elements) / len(level2_elements)
|
|
|
avg_frequency = sum((e.get('共性分析') or {}).get('出现频次', 0) for e in level2_elements) / len(level2_elements)
|
|
|
avg_intent_count = sum(calculate_intent_support_count(e) for e in level2_elements) / len(level2_elements)
|
|
|
|
|
|
@@ -427,7 +467,7 @@ def group_elements_by_hierarchical_category(elements: List[Dict[str, Any]]) -> D
|
|
|
|
|
|
|
|
|
def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, Any]] = None) -> str:
|
|
|
- """渲染单个元素项的HTML(支持详情展开,兼容新旧数据结构)
|
|
|
+ """渲染单个元素项的HTML(卡片样式,详细信息在弹窗中)
|
|
|
|
|
|
Args:
|
|
|
element: 元素数据
|
|
|
@@ -446,37 +486,15 @@ def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, An
|
|
|
elem_type = element.get('类型', '')
|
|
|
elem_type_level2 = ''
|
|
|
|
|
|
- # 获取分类(兼容新旧结构)
|
|
|
- category_data = element.get('分类', '')
|
|
|
- if isinstance(category_data, dict):
|
|
|
- category_level1 = category_data.get('一级分类', '')
|
|
|
- category_level2 = category_data.get('二级分类', '')
|
|
|
- category = get_element_category(element)
|
|
|
- elif isinstance(category_data, list):
|
|
|
- category_level1 = category_data[0] if len(category_data) > 0 else ''
|
|
|
- category_level2 = category_data[1] if len(category_data) > 1 else ''
|
|
|
- category = get_element_category(element)
|
|
|
- else:
|
|
|
- category = category_data
|
|
|
- category_level1 = ''
|
|
|
- category_level2 = ''
|
|
|
-
|
|
|
- category_def = element.get('分类定义', '')
|
|
|
# 获取共性分析(防止为None)
|
|
|
commonality = element.get('共性分析') or {}
|
|
|
coverage = commonality.get('段落覆盖率', 0.0)
|
|
|
frequency = commonality.get('出现频次', 0)
|
|
|
- paragraphs_list = commonality.get('出现段落列表', [])
|
|
|
- source = element.get('来源', [])
|
|
|
intent_count = calculate_intent_support_count(element)
|
|
|
- intent_support = get_intent_support_data(element)
|
|
|
|
|
|
- # 计算权重得分(如果有 weight_details)
|
|
|
+ # 计算权重得分
|
|
|
weight_info = compute_weight_scores(element)
|
|
|
|
|
|
- # 检查是否有详细信息
|
|
|
- has_details = bool(elem_type or category or category_def or paragraphs_list or source or intent_support or weight_info.get('raw_total', 0) > 0)
|
|
|
-
|
|
|
# 计算主导因素
|
|
|
dominant_factor = 'coverage' # 默认
|
|
|
if all_elements:
|
|
|
@@ -485,91 +503,118 @@ def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, An
|
|
|
# 根据主导因素确定边框颜色
|
|
|
border_color_class = f'dominant-{dominant_factor}'
|
|
|
|
|
|
- html = f'<li class="element-item {border_color_class}" data-elem-id="{elem_id}">\n'
|
|
|
- html += '<div class="element-header" onclick="toggleElementDetails(this)">\n'
|
|
|
-
|
|
|
- # 添加展开/收起图标
|
|
|
- if has_details:
|
|
|
- html += '<span class="element-toggle-icon">▶</span>\n'
|
|
|
-
|
|
|
- # 显示ID和名称
|
|
|
- if elem_id:
|
|
|
- html += f'<span class="element-id">#{elem_id}</span>\n'
|
|
|
- html += f'<span class="element-name">{html_module.escape(name)}</span>\n'
|
|
|
+ # 检查是否有详细信息
|
|
|
+ category_def = element.get('分类定义', '')
|
|
|
+ paragraphs_list = commonality.get('出现段落列表', [])
|
|
|
+ source = element.get('来源', [])
|
|
|
+ intent_support = get_intent_support_data(element)
|
|
|
+ has_details = bool(category_def or paragraphs_list or source or intent_support or weight_info.get('raw_total', 0) > 0)
|
|
|
|
|
|
- # 显示统计指标(根据主导因素高亮)
|
|
|
# 判断是否为形式元素
|
|
|
is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式'
|
|
|
|
|
|
- html += '<div class="element-stats">\n'
|
|
|
+ # 卡片样式
|
|
|
+ html = f'<li class="element-card {border_color_class}" data-elem-id="{elem_id}">\n'
|
|
|
+ html += '<div class="element-card-body">\n'
|
|
|
+
|
|
|
+ # 卡片头部
|
|
|
+ html += '<div class="element-card-header">\n'
|
|
|
+ if elem_id:
|
|
|
+ html += f'<span class="element-card-id">#{elem_id}</span>\n'
|
|
|
+ html += f'<h4 class="element-card-name">{html_module.escape(name)}</h4>\n'
|
|
|
+ html += '</div>\n'
|
|
|
|
|
|
- # 显示权重分(如有)
|
|
|
+ # 统计指标
|
|
|
+ html += '<div class="element-card-stats">\n'
|
|
|
if weight_info.get('raw_total', 0) > 0:
|
|
|
html += f'<span class="stat-badge stat-weight">权重分: {weight_info["weight_score"]:.1f}</span>\n'
|
|
|
-
|
|
|
+
|
|
|
if is_form:
|
|
|
- # 形式元素:显示支撑数量和权重分
|
|
|
html += f'<span class="stat-badge stat-intent">支撑: {intent_count}</span>\n'
|
|
|
- # 权重分已在上面显示
|
|
|
else:
|
|
|
- # 实质元素显示全部三个指标
|
|
|
coverage_highlight = 'stat-highlight' if dominant_factor == 'coverage' else ''
|
|
|
frequency_highlight = 'stat-highlight' if dominant_factor == 'frequency' else ''
|
|
|
intent_highlight = 'stat-highlight' if dominant_factor == 'intent_support' else ''
|
|
|
-
|
|
|
+
|
|
|
html += f'<span class="stat-badge stat-coverage {coverage_highlight}">覆盖率: {coverage:.2%}</span>\n'
|
|
|
html += f'<span class="stat-badge stat-frequency {frequency_highlight}">频次: {frequency}</span>\n'
|
|
|
html += f'<span class="stat-badge stat-intent {intent_highlight}">意图支撑: {intent_count}</span>\n'
|
|
|
-
|
|
|
html += '</div>\n'
|
|
|
|
|
|
+ # 描述
|
|
|
+ if description:
|
|
|
+ html += f'<div class="element-card-description">{html_module.escape(description)}</div>\n'
|
|
|
+
|
|
|
+ # 查看详情按钮
|
|
|
+ if has_details:
|
|
|
+ html += f'<button class="element-detail-btn" onclick="openElementModal(\'{elem_id}\')">查看详情</button>\n'
|
|
|
+
|
|
|
+ html += '</div>\n' # element-card-body
|
|
|
+ html += '</li>\n'
|
|
|
+ return html
|
|
|
+
|
|
|
+
|
|
|
+def render_element_modal(element: Dict[str, Any]) -> str:
|
|
|
+ """渲染元素详情的弹窗内容
|
|
|
+
|
|
|
+ Args:
|
|
|
+ element: 元素数据
|
|
|
+ """
|
|
|
+ elem_id = element.get('id', '')
|
|
|
+ name = element.get('名称') or ''
|
|
|
+ description = element.get('描述') or ''
|
|
|
+
|
|
|
+ # 获取类型和维度
|
|
|
+ dimension = element.get('维度', {})
|
|
|
+ if isinstance(dimension, dict):
|
|
|
+ elem_type = dimension.get('一级', '')
|
|
|
+ elem_type_level2 = dimension.get('二级', '')
|
|
|
+ else:
|
|
|
+ elem_type = element.get('类型', '')
|
|
|
+ elem_type_level2 = ''
|
|
|
+
|
|
|
+ # 获取分类
|
|
|
+ category_data = element.get('分类', '')
|
|
|
+ category_def = element.get('分类定义', '')
|
|
|
+
|
|
|
+ # 获取共性分析
|
|
|
+ commonality = element.get('共性分析') or {}
|
|
|
+ coverage = commonality.get('段落覆盖率', 0.0)
|
|
|
+ frequency = commonality.get('出现频次', 0)
|
|
|
+ paragraphs_list = commonality.get('出现段落列表', [])
|
|
|
+ source = element.get('来源', [])
|
|
|
+ intent_support = get_intent_support_data(element)
|
|
|
+ weight_info = compute_weight_scores(element)
|
|
|
+
|
|
|
+ html = f'<div class="element-modal-content" data-elem-id="{elem_id}">\n'
|
|
|
+ html += '<div class="modal-header">\n'
|
|
|
+ if elem_id:
|
|
|
+ html += f'<span class="modal-id">#{elem_id}</span>\n'
|
|
|
+ html += f'<h3 class="modal-title">{html_module.escape(name)}</h3>\n'
|
|
|
+ html += '<button class="modal-close" onclick="closeElementModal()">×</button>\n'
|
|
|
html += '</div>\n'
|
|
|
|
|
|
- # 描述(始终显示)
|
|
|
+ html += '<div class="modal-body">\n'
|
|
|
+
|
|
|
+ # 描述
|
|
|
if description:
|
|
|
- html += f'<div class="element-description">{html_module.escape(description)}</div>\n'
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>描述:</strong>\n'
|
|
|
+ html += f'<div class="detail-text">{html_module.escape(description)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
|
|
|
- # 详细信息(可展开)
|
|
|
- if has_details:
|
|
|
- html += '<div class="element-details collapsed">\n'
|
|
|
-
|
|
|
- # 维度/类型 - 已移除,不再展示
|
|
|
- # if elem_type:
|
|
|
- # html += '<div class="detail-section">\n'
|
|
|
- # html += '<strong>维度:</strong>\n'
|
|
|
- # html += '<div class="detail-content">\n'
|
|
|
- # html += f'<span class="detail-tag dimension-level1">{html_module.escape(elem_type)}</span>\n'
|
|
|
- # if elem_type_level2:
|
|
|
- # html += f'<span class="detail-tag dimension-level2">{html_module.escape(elem_type_level2)}</span>\n'
|
|
|
- # html += '</div>\n'
|
|
|
- # html += '</div>\n'
|
|
|
-
|
|
|
- # 分类 - 已移除,不再展示
|
|
|
- # if category_level1 or category:
|
|
|
- # html += '<div class="detail-section">\n'
|
|
|
- # html += '<strong>分类:</strong>\n'
|
|
|
- # html += '<div class="detail-content">\n'
|
|
|
- # if category_level1:
|
|
|
- # html += f'<span class="detail-tag category-level1">{html_module.escape(category_level1)}</span>\n'
|
|
|
- # if category_level2:
|
|
|
- # html += f'<span class="detail-tag category-level2">{html_module.escape(category_level2)}</span>\n'
|
|
|
- # else:
|
|
|
- # html += f'<span class="detail-tag">{html_module.escape(str(category))}</span>\n'
|
|
|
- # html += '</div>\n'
|
|
|
- # html += '</div>\n'
|
|
|
-
|
|
|
- # 分类定义
|
|
|
- if category_def:
|
|
|
- html += '<div class="detail-section">\n'
|
|
|
- html += '<strong>分类定义:</strong>\n'
|
|
|
- html += f'<div class="detail-text">{html_module.escape(category_def)}</div>\n'
|
|
|
- html += '</div>\n'
|
|
|
+ # 分类定义
|
|
|
+ if category_def:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>分类定义:</strong>\n'
|
|
|
+ html += f'<div class="detail-text">{html_module.escape(category_def)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
|
|
|
- # 针对"形式"维度,显示"支撑"、"推理"和"支撑关系"
|
|
|
- if isinstance(dimension, dict) and dimension.get('一级') == '形式':
|
|
|
- # 支撑
|
|
|
- zhicheng = element.get('支撑')
|
|
|
- if zhicheng:
|
|
|
+ # 针对"形式"维度,显示"支撑"、"推理"和"支撑关系"
|
|
|
+ if isinstance(dimension, dict) and dimension.get('一级') == '形式':
|
|
|
+ # 支撑
|
|
|
+ zhicheng = element.get('支撑')
|
|
|
+ if zhicheng:
|
|
|
html += '<div class="detail-section">\n'
|
|
|
html += '<strong>支撑:</strong>\n'
|
|
|
html += '<div class="detail-content">\n'
|
|
|
@@ -600,22 +645,21 @@ def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, An
|
|
|
html += '</div>\n'
|
|
|
html += '</div>\n'
|
|
|
|
|
|
- # 推理
|
|
|
- tuili = element.get('推理')
|
|
|
- if tuili:
|
|
|
+ # 推理
|
|
|
+ tuili = element.get('推理')
|
|
|
+ if tuili:
|
|
|
html += '<div class="detail-section">\n'
|
|
|
html += '<strong>推理:</strong>\n'
|
|
|
html += f'<div class="detail-text">{html_module.escape(tuili)}</div>\n'
|
|
|
html += '</div>\n'
|
|
|
|
|
|
- # 支撑关系(形式元素专用,从意图支撑数据中显示支撑点)
|
|
|
- # 优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
|
|
|
- intent_support_data = get_intent_support_data(element)
|
|
|
- if intent_support_data:
|
|
|
- html += '<div class="detail-section">\n'
|
|
|
- html += '<strong>支撑关系:</strong>\n'
|
|
|
-
|
|
|
- for point_type in ['灵感点', '目的点', '关键点']:
|
|
|
+ # 支撑关系(形式元素专用,从意图支撑数据中显示支撑点)
|
|
|
+ # 优先使用"意图支撑"字段,如果没有则使用"多维度评分"字段
|
|
|
+ intent_support_data = get_intent_support_data(element)
|
|
|
+ if intent_support_data:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>支撑关系:</strong>\n'
|
|
|
+ for point_type in ['灵感点', '目的点', '关键点']:
|
|
|
support_points = intent_support_data.get(point_type) or []
|
|
|
if not support_points:
|
|
|
continue
|
|
|
@@ -651,8 +695,7 @@ def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, An
|
|
|
html += '</div>\n'
|
|
|
|
|
|
html += '</div>\n' # end score-list
|
|
|
-
|
|
|
- html += '</div>\n' # end detail-section
|
|
|
+ html += '</div>\n' # end detail-section
|
|
|
|
|
|
# 针对"隐含概念",显示"来源"(声音特征、语气语调、BGM、音效等)和"时间范围"
|
|
|
elem_type = element.get('类型', '')
|
|
|
@@ -888,11 +931,342 @@ def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, An
|
|
|
return html
|
|
|
|
|
|
|
|
|
+def render_element_modal(element: Dict[str, Any]) -> str:
|
|
|
+ """渲染元素详情的弹窗内容
|
|
|
+
|
|
|
+ Args:
|
|
|
+ element: 元素数据
|
|
|
+ """
|
|
|
+ elem_id = element.get('id', '')
|
|
|
+ name = element.get('名称') or ''
|
|
|
+ description = element.get('描述') or ''
|
|
|
+
|
|
|
+ # 获取类型和维度
|
|
|
+ dimension = element.get('维度', {})
|
|
|
+ if isinstance(dimension, dict):
|
|
|
+ elem_type = dimension.get('一级', '')
|
|
|
+ elem_type_level2 = dimension.get('二级', '')
|
|
|
+ else:
|
|
|
+ elem_type = element.get('类型', '')
|
|
|
+ elem_type_level2 = ''
|
|
|
+
|
|
|
+ # 获取分类
|
|
|
+ category_data = element.get('分类', '')
|
|
|
+ category_def = element.get('分类定义', '')
|
|
|
+
|
|
|
+ # 获取共性分析
|
|
|
+ commonality = element.get('共性分析') or {}
|
|
|
+ coverage = commonality.get('段落覆盖率', 0.0)
|
|
|
+ frequency = commonality.get('出现频次', 0)
|
|
|
+ paragraphs_list = commonality.get('出现段落列表', [])
|
|
|
+ source = element.get('来源', [])
|
|
|
+ intent_support = get_intent_support_data(element)
|
|
|
+ weight_info = compute_weight_scores(element)
|
|
|
+
|
|
|
+ html = f'<div class="element-modal-content" data-elem-id="{elem_id}">\n'
|
|
|
+ html += '<div class="modal-header">\n'
|
|
|
+ if elem_id:
|
|
|
+ html += f'<span class="modal-id">#{elem_id}</span>\n'
|
|
|
+ html += f'<h3 class="modal-title">{html_module.escape(name)}</h3>\n'
|
|
|
+ html += '<button class="modal-close" onclick="closeElementModal()">×</button>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ html += '<div class="modal-body">\n'
|
|
|
+
|
|
|
+ # 描述
|
|
|
+ if description:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>描述:</strong>\n'
|
|
|
+ html += f'<div class="detail-text">{html_module.escape(description)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 分类定义
|
|
|
+ if category_def:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>分类定义:</strong>\n'
|
|
|
+ html += f'<div class="detail-text">{html_module.escape(category_def)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 针对"形式"维度,显示"支撑"、"推理"和"支撑关系"
|
|
|
+ if isinstance(dimension, dict) and dimension.get('一级') == '形式':
|
|
|
+ # 支撑
|
|
|
+ zhicheng = element.get('支撑')
|
|
|
+ if zhicheng:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>支撑:</strong>\n'
|
|
|
+ html += '<div class="detail-content">\n'
|
|
|
+ if isinstance(zhicheng, list):
|
|
|
+ for item in zhicheng:
|
|
|
+ if isinstance(item, dict):
|
|
|
+ item_id = item.get('id', '')
|
|
|
+ item_name = item.get('名称', '')
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(f"{item_id}: {item_name}")}</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
|
|
|
+ elif isinstance(zhicheng, dict):
|
|
|
+ for key, values in zhicheng.items():
|
|
|
+ if isinstance(values, list):
|
|
|
+ html += f'<div class="detail-tag category-level1">{html_module.escape(key)}</div>\n'
|
|
|
+ for item in values:
|
|
|
+ if isinstance(item, dict):
|
|
|
+ item_id = item.get('id', '')
|
|
|
+ item_name = item.get('名称', '')
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(f"{item_id}: {item_name}")}</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(str(values))}</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(str(zhicheng))}</span>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 推理
|
|
|
+ tuili = element.get('推理')
|
|
|
+ if tuili:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>推理:</strong>\n'
|
|
|
+ html += f'<div class="detail-text">{html_module.escape(tuili)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 支撑关系
|
|
|
+ intent_support_data = get_intent_support_data(element)
|
|
|
+ if intent_support_data:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>支撑关系:</strong>\n'
|
|
|
+ for point_type in ['灵感点', '目的点', '关键点']:
|
|
|
+ support_points = intent_support_data.get(point_type) or []
|
|
|
+ if not support_points:
|
|
|
+ continue
|
|
|
+ html += f'<div class="score-type">{point_type}</div>\n'
|
|
|
+ html += '<div class="score-list">\n'
|
|
|
+ for support_point in support_points:
|
|
|
+ if not isinstance(support_point, dict):
|
|
|
+ continue
|
|
|
+ point = support_point.get('点', '')
|
|
|
+ point_intention = support_point.get('点的意图', '')
|
|
|
+ support_reason = support_point.get('支撑理由', '')
|
|
|
+ html += '<div class="score-item">\n'
|
|
|
+ html += f'<div class="score-point">{html_module.escape(point)}</div>\n'
|
|
|
+ if point_intention:
|
|
|
+ html += '<div class="point-intention">\n'
|
|
|
+ html += f'<strong style="color: #666;">点的意图:</strong>{html_module.escape(point_intention)}\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ if support_reason:
|
|
|
+ html += '<div class="score-reasons">\n'
|
|
|
+ html += f'<strong style="color: #666;">支撑理由:</strong>\n'
|
|
|
+ html += f'<div class="score-reason">{html_module.escape(support_reason)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 针对"隐含概念"
|
|
|
+ elem_type = element.get('类型', '')
|
|
|
+ if elem_type == '隐含概念':
|
|
|
+ laiyuan = element.get('来源')
|
|
|
+ if laiyuan and isinstance(laiyuan, dict):
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>来源:</strong>\n'
|
|
|
+ html += '<div class="detail-content">\n'
|
|
|
+ for key, values in laiyuan.items():
|
|
|
+ if isinstance(values, list) and values:
|
|
|
+ html += f'<div class="detail-tag category-level1">{html_module.escape(key)}</div>\n'
|
|
|
+ for item in values:
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
|
|
|
+ elif values:
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(str(values))}</span>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ time_range = element.get('时间范围')
|
|
|
+ if time_range:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>时间范围:</strong>\n'
|
|
|
+ html += '<div class="detail-content">\n'
|
|
|
+ if isinstance(time_range, list):
|
|
|
+ for tr in time_range:
|
|
|
+ html += f'<span class="detail-tag source-tag">{html_module.escape(str(tr))}</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="detail-tag source-tag">{html_module.escape(str(time_range))}</span>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 针对"抽象概念"
|
|
|
+ elif isinstance(dimension, dict) and dimension.get('二级') == '抽象概念':
|
|
|
+ leixing = element.get('类型')
|
|
|
+ if leixing:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>类型:</strong>\n'
|
|
|
+ html += f'<div class="detail-text">{html_module.escape(str(leixing))}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ laiyuan = element.get('来源')
|
|
|
+ if laiyuan and isinstance(laiyuan, dict):
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>来源:</strong>\n'
|
|
|
+ html += '<div class="detail-content">\n'
|
|
|
+ for key, values in laiyuan.items():
|
|
|
+ if isinstance(values, list):
|
|
|
+ html += f'<div class="detail-tag category-level1">{html_module.escape(key)}</div>\n'
|
|
|
+ for item in values:
|
|
|
+ if isinstance(item, dict):
|
|
|
+ item_id = item.get('id', '')
|
|
|
+ item_name = item.get('名称', '')
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(f"{item_id}: {item_name}")}</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(str(values))}</span>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ tuili_guocheng = element.get('推理过程')
|
|
|
+ if tuili_guocheng:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>推理过程:</strong>\n'
|
|
|
+ html += f'<div class="detail-text">{html_module.escape(tuili_guocheng)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ else:
|
|
|
+ if source:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>来源:</strong>\n'
|
|
|
+ html += '<div class="detail-content">\n'
|
|
|
+ for src in source:
|
|
|
+ html += f'<span class="detail-tag source-tag">{html_module.escape(str(src))}</span>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 上下文验证
|
|
|
+ context_verification = element.get('上下文验证')
|
|
|
+ if context_verification:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>上下文验证:</strong>\n'
|
|
|
+ html += '<div class="context-verification">\n'
|
|
|
+ original_position = context_verification.get('原文位置', '')
|
|
|
+ if original_position:
|
|
|
+ html += '<div class="context-item">\n'
|
|
|
+ html += '<strong>原文位置:</strong>\n'
|
|
|
+ html += f'<div class="context-text">{html_module.escape(original_position)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ grammar_component = context_verification.get('语法成分', '')
|
|
|
+ if grammar_component:
|
|
|
+ html += '<div class="context-item">\n'
|
|
|
+ html += '<strong>语法成分:</strong>\n'
|
|
|
+ html += f'<span class="grammar-tag">{html_module.escape(grammar_component)}</span>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ context_judgment = context_verification.get('语境判断', '')
|
|
|
+ if context_judgment:
|
|
|
+ html += '<div class="context-item">\n'
|
|
|
+ html += '<strong>语境判断:</strong>\n'
|
|
|
+ html += f'<div class="context-text">{html_module.escape(context_judgment)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 出现段落
|
|
|
+ if paragraphs_list:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>出现段落:</strong>\n'
|
|
|
+ html += '<div class="paragraphs-detail-list">\n'
|
|
|
+ for para in paragraphs_list:
|
|
|
+ if isinstance(para, dict):
|
|
|
+ para_id = para.get('段落ID', '')
|
|
|
+ how = para.get('如何体现', '')
|
|
|
+ html += '<div class="paragraph-detail-item">\n'
|
|
|
+ html += f'<span class="detail-tag para-id-tag">{html_module.escape(para_id)}</span>\n'
|
|
|
+ if how:
|
|
|
+ html += f'<div class="para-how">{html_module.escape(how)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="detail-tag">{html_module.escape(str(para))}</span>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 意图支撑
|
|
|
+ if intent_support:
|
|
|
+ html += '<div class="detail-section">\n'
|
|
|
+ html += '<strong>意图支撑:</strong>\n'
|
|
|
+ for point_type in ['灵感点', '目的点', '关键点']:
|
|
|
+ if point_type in intent_support and intent_support[point_type]:
|
|
|
+ html += f'<div class="score-type">{point_type}</div>\n'
|
|
|
+ html += '<div class="score-list">\n'
|
|
|
+ for item in intent_support[point_type]:
|
|
|
+ point = item.get('点', '')
|
|
|
+ point_intention = item.get('点的意图', '')
|
|
|
+ support_reason = item.get('支撑理由', '')
|
|
|
+ html += '<div class="score-item">\n'
|
|
|
+ html += f'<div class="score-point">{html_module.escape(point)}</div>\n'
|
|
|
+ if point_intention:
|
|
|
+ html += '<div class="point-intention">\n'
|
|
|
+ html += f'<strong style="color: #666;">点的意图:</strong>{html_module.escape(point_intention)}\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ if support_reason:
|
|
|
+ html += '<div class="score-reasons">\n'
|
|
|
+ html += f'<strong style="color: #666;">支撑理由:</strong>\n'
|
|
|
+ html += f'<div class="score-reason">{html_module.escape(support_reason)}</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 权重明细
|
|
|
+ if weight_info.get('raw_total', 0) > 0:
|
|
|
+ wd = weight_info['details']
|
|
|
+ ss = weight_info['support_stats']
|
|
|
+ is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式'
|
|
|
+
|
|
|
+ html += '<div class="detail-section weight-detail-section">\n'
|
|
|
+ html += '<strong>权重得分明细:</strong>\n'
|
|
|
+ html += '<div class="weight-summary-header">\n'
|
|
|
+ if is_form:
|
|
|
+ html += f'权重分:{weight_info["weight_score"]:.1f}(形式元素使用新的权重计算逻辑)\n'
|
|
|
+ else:
|
|
|
+ html += f'原始总分:{weight_info["raw_total"]:.1f},权重分:{weight_info["weight_score"]:.1f}(min(100, 原始总分 × 100 / 110))\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '<div class="weight-summary-grid">\n'
|
|
|
+
|
|
|
+ if is_form:
|
|
|
+ html += f'<span class="weight-chip">频次分 {wd.get("频次分", 0):.1f}</span>\n'
|
|
|
+ if "覆盖段落数分" in wd:
|
|
|
+ html += f'<span class="weight-chip">覆盖段落数分 {wd.get("覆盖段落数分", 0):.1f}</span>\n'
|
|
|
+ html += f'<span class="weight-chip">覆盖率分 {wd.get("覆盖率分", 0):.1f}</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="weight-chip">频次分 {wd.get("频次分", 0):.1f} / 30</span>\n'
|
|
|
+ html += f'<span class="weight-chip">覆盖率分 {wd.get("覆盖率分", 0):.1f} / 30</span>\n'
|
|
|
+
|
|
|
+ inspiration_count = ss.get('灵感点数量', 0)
|
|
|
+ if is_form:
|
|
|
+ html += f'<span class="weight-chip">灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f}({inspiration_count} 个)</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="weight-chip">灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f} / 25({inspiration_count} 个)</span>\n'
|
|
|
+
|
|
|
+ purpose_count = ss.get('目的点数量', 0)
|
|
|
+ if is_form:
|
|
|
+ html += f'<span class="weight-chip">目的点支撑分 {wd.get("目的点支撑分", 0):.1f}({purpose_count} 个)</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="weight-chip">目的点支撑分 {wd.get("目的点支撑分", 0):.1f} / 15({purpose_count} 个)</span>\n'
|
|
|
+
|
|
|
+ keypoint_count = ss.get('关键点数量', 0)
|
|
|
+ if is_form:
|
|
|
+ html += f'<span class="weight-chip">关键点支撑分 {wd.get("关键点支撑分", 0):.1f}({keypoint_count} 个)</span>\n'
|
|
|
+ else:
|
|
|
+ html += f'<span class="weight-chip">关键点支撑分 {wd.get("关键点支撑分", 0):.1f} / 10({keypoint_count} 个)</span>\n'
|
|
|
+
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ html += '</div>\n' # modal-body
|
|
|
+ html += '</div>\n' # element-modal-content
|
|
|
+ return html
|
|
|
+
|
|
|
+
|
|
|
def generate_tab3_content(data: Dict[str, Any]) -> str:
|
|
|
"""生成Tab3内容:按层次展示(实质/形式 → 具体元素/具体概念/抽象概念 → 树形展示)"""
|
|
|
html = '<div class="tab-content" id="tab3" style="display:none;">\n'
|
|
|
|
|
|
- # 添加全局控制按钮
|
|
|
+ # 添加全局控制按钮(移除展开/收起按钮)
|
|
|
html += '<div class="global-controls">\n'
|
|
|
html += ' <div class="color-legend">\n'
|
|
|
html += ' <span class="legend-title">颜色图例(主导因素):</span>\n'
|
|
|
@@ -900,10 +1274,11 @@ def generate_tab3_content(data: Dict[str, Any]) -> str:
|
|
|
html += ' <span class="legend-item legend-frequency">频次</span>\n'
|
|
|
html += ' <span class="legend-item legend-intent">意图支撑</span>\n'
|
|
|
html += ' </div>\n'
|
|
|
- html += ' <button class="control-btn" onclick="toggleAllLevels(true)">全部展开</button>\n'
|
|
|
- html += ' <button class="control-btn" onclick="toggleAllLevels(false)">全部收起</button>\n'
|
|
|
html += '</div>\n'
|
|
|
|
|
|
+ # 收集所有元素用于弹窗
|
|
|
+ all_elements_dict = {}
|
|
|
+
|
|
|
if '脚本理解' in data:
|
|
|
script = data['脚本理解']
|
|
|
# 尝试获取元素列表,如果不存在则合并实质列表和形式列表
|
|
|
@@ -913,6 +1288,12 @@ def generate_tab3_content(data: Dict[str, Any]) -> str:
|
|
|
form_list = script.get('形式列表', [])
|
|
|
elements = substance_list + form_list
|
|
|
|
|
|
+ # 收集所有元素到字典中(用于弹窗)
|
|
|
+ for elem in elements:
|
|
|
+ elem_id = elem.get('id', '')
|
|
|
+ if elem_id:
|
|
|
+ all_elements_dict[elem_id] = elem
|
|
|
+
|
|
|
# 第一层:按维度.一级分组(实质 vs 形式)
|
|
|
level1_groups = {}
|
|
|
for elem in elements:
|
|
|
@@ -998,7 +1379,7 @@ def generate_tab3_content(data: Dict[str, Any]) -> str:
|
|
|
continue
|
|
|
|
|
|
# 计算一级分类的统计信息
|
|
|
- avg_coverage = sum((e.get('共性分析') or {}).get('段落覆盖率', 0.0) for e in all_cat_elements) / len(all_cat_elements)
|
|
|
+ avg_coverage = sum(get_element_coverage(e) for e in all_cat_elements) / len(all_cat_elements)
|
|
|
avg_intent_count = sum(calculate_intent_support_count(e) for e in all_cat_elements) / len(all_cat_elements)
|
|
|
|
|
|
html += '<div class="category-group collapsible">\n'
|
|
|
@@ -1026,7 +1407,7 @@ def generate_tab3_content(data: Dict[str, Any]) -> str:
|
|
|
continue
|
|
|
|
|
|
# 计算二级分类的统计信息
|
|
|
- avg_coverage_l2 = sum((e.get('共性分析') or {}).get('段落覆盖率', 0.0) for e in cat_level2_elements) / len(cat_level2_elements)
|
|
|
+ avg_coverage_l2 = sum(get_element_coverage(e) for e in cat_level2_elements) / len(cat_level2_elements)
|
|
|
avg_intent_count_l2 = sum(calculate_intent_support_count(e) for e in cat_level2_elements) / len(cat_level2_elements)
|
|
|
|
|
|
html += '<div class="subcategory-group collapsible">\n'
|
|
|
@@ -1055,5 +1436,491 @@ def generate_tab3_content(data: Dict[str, Any]) -> str:
|
|
|
html += '</div>\n' # level1-content
|
|
|
html += '</div>\n' # level1-section
|
|
|
|
|
|
+ # 预生成所有元素的弹窗内容(隐藏)
|
|
|
+ html += '<div id="elementModalTemplates" style="display:none;">\n'
|
|
|
+ for elem_id, elem in all_elements_dict.items():
|
|
|
+ html += render_element_modal(elem)
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 添加弹窗结构
|
|
|
+ html += '<div id="elementModal" class="element-modal" style="display:none;">\n'
|
|
|
+ html += '<div class="element-modal-overlay" onclick="closeElementModal()"></div>\n'
|
|
|
+ html += '<div class="element-modal-dialog">\n'
|
|
|
+ html += '<div id="elementModalContent" class="element-modal-content-wrapper">\n'
|
|
|
+ html += '<!-- 弹窗内容将通过JavaScript动态填充 -->\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+ html += '</div>\n'
|
|
|
+
|
|
|
+ # 添加CSS样式
|
|
|
+ html += '<style>\n'
|
|
|
+ html += '''
|
|
|
+ /* 元素卡片样式 */
|
|
|
+ .element-list {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
|
+ gap: 20px;
|
|
|
+ list-style: none;
|
|
|
+ padding: 0;
|
|
|
+ margin: 20px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card {
|
|
|
+ background: white;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card:hover {
|
|
|
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
|
+ transform: translateY(-2px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card.dominant-coverage {
|
|
|
+ border-left: 4px solid #667eea;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card.dominant-frequency {
|
|
|
+ border-left: 4px solid #f093fb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card.dominant-intent_support {
|
|
|
+ border-left: 4px solid #4facfe;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card-body {
|
|
|
+ padding: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card-id {
|
|
|
+ color: #667eea;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card-name {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card-stats {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-badge {
|
|
|
+ display: inline-block;
|
|
|
+ padding: 4px 10px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-badge.stat-weight {
|
|
|
+ background: #667eea;
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-badge.stat-coverage {
|
|
|
+ background: #e8f0ff;
|
|
|
+ color: #667eea;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-badge.stat-frequency {
|
|
|
+ background: #fce4ec;
|
|
|
+ color: #f093fb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-badge.stat-intent {
|
|
|
+ background: #e0f7fa;
|
|
|
+ color: #4facfe;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-badge.stat-highlight {
|
|
|
+ font-weight: 700;
|
|
|
+ transform: scale(1.05);
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-card-description {
|
|
|
+ color: #666;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.6;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-detail-btn {
|
|
|
+ width: 100%;
|
|
|
+ padding: 10px;
|
|
|
+ background: #667eea;
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-detail-btn:hover {
|
|
|
+ background: #5568d3;
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 弹窗样式 */
|
|
|
+ .element-modal {
|
|
|
+ display: none;
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ z-index: 1000;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-modal-overlay {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-modal-dialog {
|
|
|
+ position: relative;
|
|
|
+ width: 800px;
|
|
|
+ height: 90vh;
|
|
|
+ background: white;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
|
|
+ z-index: 1001;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-modal-content-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-modal-content {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-header {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 20px 24px;
|
|
|
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ color: white;
|
|
|
+ border-radius: 12px 12px 0 0;
|
|
|
+ position: sticky;
|
|
|
+ top: 0;
|
|
|
+ z-index: 10;
|
|
|
+ flex-shrink: 0;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-id {
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 14px;
|
|
|
+ opacity: 0.9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-title {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-close {
|
|
|
+ background: rgba(255, 255, 255, 0.2);
|
|
|
+ border: none;
|
|
|
+ color: white;
|
|
|
+ font-size: 24px;
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ border-radius: 50%;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-close:hover {
|
|
|
+ background: rgba(255, 255, 255, 0.3);
|
|
|
+ transform: rotate(90deg);
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-body {
|
|
|
+ width: 100%;
|
|
|
+ flex: 1;
|
|
|
+ padding: 24px;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ min-height: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-section {
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-section strong {
|
|
|
+ display: block;
|
|
|
+ color: #667eea;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-text {
|
|
|
+ color: #555;
|
|
|
+ line-height: 1.6;
|
|
|
+ padding: 12px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 6px;
|
|
|
+ border-left: 3px solid #667eea;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-content {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+ margin-top: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-tag {
|
|
|
+ display: inline-block;
|
|
|
+ padding: 6px 12px;
|
|
|
+ background: #e8f0ff;
|
|
|
+ color: #667eea;
|
|
|
+ border-radius: 16px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-tag.category-level1 {
|
|
|
+ width: 100%;
|
|
|
+ background: #667eea;
|
|
|
+ color: white;
|
|
|
+ font-weight: 600;
|
|
|
+ margin-top: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-tag.source-tag {
|
|
|
+ background: #fce4ec;
|
|
|
+ color: #f093fb;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-tag.para-id-tag {
|
|
|
+ background: #e0f7fa;
|
|
|
+ color: #4facfe;
|
|
|
+ }
|
|
|
+
|
|
|
+ .weight-chip {
|
|
|
+ display: inline-block;
|
|
|
+ padding: 6px 12px;
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #333;
|
|
|
+ border-radius: 16px;
|
|
|
+ font-size: 12px;
|
|
|
+ margin: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .weight-summary-grid {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+ margin-top: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .weight-summary-header {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #666;
|
|
|
+ margin-top: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .weight-detail-section {
|
|
|
+ margin-top: 20px;
|
|
|
+ padding-top: 20px;
|
|
|
+ border-top: 1px dashed #e0e0e0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .score-type {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #667eea;
|
|
|
+ margin-top: 16px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .score-list {
|
|
|
+ margin-left: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .score-item {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ padding: 12px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 6px;
|
|
|
+ border-left: 3px solid #667eea;
|
|
|
+ }
|
|
|
+
|
|
|
+ .score-point {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .point-intention, .score-reasons {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+
|
|
|
+ .score-reason {
|
|
|
+ margin-top: 4px;
|
|
|
+ padding: 8px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .context-verification {
|
|
|
+ margin-top: 8px;
|
|
|
+ padding: 12px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 4px;
|
|
|
+ border-left: 3px solid #6c757d;
|
|
|
+ }
|
|
|
+
|
|
|
+ .context-item {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .context-text {
|
|
|
+ margin-top: 4px;
|
|
|
+ padding: 6px 10px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 3px;
|
|
|
+ color: #212529;
|
|
|
+ font-style: italic;
|
|
|
+ }
|
|
|
+
|
|
|
+ .grammar-tag {
|
|
|
+ margin-left: 8px;
|
|
|
+ padding: 3px 10px;
|
|
|
+ background: #e7f3ff;
|
|
|
+ color: #0066cc;
|
|
|
+ border-radius: 3px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .paragraphs-detail-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+ margin-top: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .paragraph-detail-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .para-how {
|
|
|
+ flex: 1;
|
|
|
+ padding: 8px;
|
|
|
+ background: white;
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #555;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 768px) {
|
|
|
+ .element-list {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-modal-dialog {
|
|
|
+ width: 95%;
|
|
|
+ height: 95vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ .modal-body {
|
|
|
+ padding: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ '''
|
|
|
+ html += '</style>\n'
|
|
|
+
|
|
|
+ # 添加JavaScript代码
|
|
|
+ html += '<script>\n'
|
|
|
+ html += '''
|
|
|
+ function openElementModal(elemId) {
|
|
|
+ const modal = document.getElementById('elementModal');
|
|
|
+ const contentWrapper = document.getElementById('elementModalContent');
|
|
|
+ const templates = document.getElementById('elementModalTemplates');
|
|
|
+
|
|
|
+ // 查找预生成的弹窗内容
|
|
|
+ const modalContent = templates.querySelector(`.element-modal-content[data-elem-id="${elemId}"]`);
|
|
|
+ if (modalContent) {
|
|
|
+ contentWrapper.innerHTML = modalContent.outerHTML;
|
|
|
+ modal.style.display = 'flex';
|
|
|
+ document.body.style.overflow = 'hidden';
|
|
|
+ } else {
|
|
|
+ console.error('Modal content not found for element:', elemId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeElementModal() {
|
|
|
+ const modal = document.getElementById('elementModal');
|
|
|
+ modal.style.display = 'none';
|
|
|
+ document.body.style.overflow = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // ESC键关闭弹窗
|
|
|
+ document.addEventListener('keydown', function(e) {
|
|
|
+ if (e.key === 'Escape') {
|
|
|
+ closeElementModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ '''
|
|
|
+ html += '</script>\n'
|
|
|
+
|
|
|
html += '</div>\n'
|
|
|
return html
|