Files

584 lines
25 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* DeerFlow generated-HTML runtime. Browser ES5; no module loader required. */
(function () {
"use strict";
function ready(fn) {
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", fn);
else fn();
}
function blocks(name) {
return document.querySelectorAll('script[type="application/json"][' + name + "]");
}
function parse(block, target) {
try {
return JSON.parse(block.textContent || "{}");
} catch (error) {
showError(target, "图形数据格式错误:" + (error && error.message ? error.message : "无法解析 JSON"));
return null;
}
}
function showError(target, message) {
var box = document.createElement("div");
box.className = "df-runtime-error";
box.setAttribute("role", "alert");
box.textContent = message;
if (target && target.parentNode) target.parentNode.insertBefore(box, target.nextSibling);
}
function size(target) {
if (target.clientHeight < 40) target.style.minHeight = "360px";
if (target.clientWidth < 40) target.style.width = "100%";
}
// 多页面门户会用 display:none 隐藏非当前页;此时 G6/ECharts 以 0 宽初始化,
// 即使稍后页面显示也只会在浏览器 resize 时才恢复。等容器真正可见后再
// 创建实例,并把之后的面板/iframe 尺寸变化同步给图表和流程图。
function closestLogicalPage(target) {
var node = target;
while (node && node !== document.body) {
if (node.classList && (node.classList.contains("page") || node.classList.contains("view"))) {
return node;
}
node = node.parentNode;
}
return null;
}
function whenVisible(target, callback) {
var observer = null;
var intersection = null;
var attempts = 0;
var timer = null;
var completed = false;
function cleanup() {
if (observer) observer.disconnect();
if (intersection) intersection.disconnect();
if (timer) window.clearTimeout(timer);
observer = null;
intersection = null;
timer = null;
window.removeEventListener("hashchange", check);
}
function check() {
if (completed || !target || target.clientWidth < 40 || target.clientHeight < 40) {
return false;
}
completed = true;
cleanup();
callback();
return true;
}
if (check()) return;
if (window.ResizeObserver) {
observer = new window.ResizeObserver(check);
observer.observe(target);
var page = closestLogicalPage(target);
if (page) observer.observe(page);
}
if (window.IntersectionObserver) {
intersection = new window.IntersectionObserver(function (entries) {
for (var i = 0; i < entries.length; i += 1) {
if (entries[i].isIntersecting) check();
}
}, { threshold: 0 });
intersection.observe(target);
}
window.addEventListener("hashchange", check);
if (window.ResizeObserver) return;
(function poll() {
if (check() || attempts >= 120) return;
attempts += 1;
timer = window.setTimeout(poll, 50);
})();
}
function bindCanvasResize(target, resize) {
var frame = null;
function schedule() {
if (frame !== null) {
window.cancelAnimationFrame(frame);
}
frame = window.requestAnimationFrame(function () {
frame = null;
if (target.clientWidth >= 40 && target.clientHeight >= 40) {
resize();
}
});
}
if (window.ResizeObserver) {
(new window.ResizeObserver(schedule)).observe(target);
}
window.addEventListener("resize", schedule);
window.addEventListener("hashchange", schedule);
}
function bindEChartsResize(target, chart) {
var frame = null;
function resize() {
if (frame !== null) {
window.cancelAnimationFrame(frame);
}
frame = window.requestAnimationFrame(function () {
frame = null;
if (target.clientWidth >= 40 && target.clientHeight >= 40 && !chart.isDisposed()) {
chart.resize();
}
});
}
bindCanvasResize(target, resize);
}
function escapeHtml(value) {
return String(value == null ? "" : value).replace(/[&<>"']/g, function (ch) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[ch];
});
}
function geoPoint(value) {
if (!value || value.length < 2) return null;
var lng = Number(value[0]);
var lat = Number(value[1]);
if (!isFinite(lng) || !isFinite(lat) || lng < -180 || lng > 180 || lat < -90 || lat > 90) return null;
return [lng, lat];
}
function leafletPoint(value) {
var coordinate = geoPoint(value);
return coordinate ? [coordinate[1], coordinate[0]] : null;
}
function initCharts() {
var items = blocks("data-df-chart");
for (var i = 0; i < items.length; i += 1) {
var block = items[i];
var target = document.getElementById(block.getAttribute("data-df-chart"));
if (!target) continue;
var option = parse(block, target);
if (!option) continue;
if (!window.echarts) {
showError(target, "ECharts 运行库加载失败");
continue;
}
size(target);
(function (chartTarget, chartOption) {
whenVisible(chartTarget, function () {
try {
chartTarget.setAttribute("role", "img");
chartTarget.setAttribute("aria-label", chartOption.title && chartOption.title.text ? chartOption.title.text : "数据图表");
var chart = window.echarts.init(chartTarget);
chart.setOption(chartOption);
bindEChartsResize(chartTarget, chart);
} catch (error) {
showError(chartTarget, "图表渲染失败:" + (error && error.message ? error.message : "请检查图表数据"));
}
});
})(target, option);
}
}
var geoMapCache = {};
var geoMapLoads = {};
var geoMapDefaultViews = {
"us-states": { center: [-98, 38], zoom: 2.1 }
};
function geoMapUrl(mapId) {
var config = window.DEERFLOW_HTML_RUNTIME_CONFIG || {};
var maps = config.geoMaps || {};
return typeof maps[mapId] === "string" ? maps[mapId] : "";
}
function loadGeoMap(mapId) {
if (geoMapCache[mapId]) return Promise.resolve(geoMapCache[mapId]);
if (geoMapLoads[mapId]) return geoMapLoads[mapId];
var url = geoMapUrl(mapId);
if (!url) return Promise.reject(new Error("未配置地图资源:" + mapId));
if (!window.fetch) return Promise.reject(new Error("当前浏览器不支持加载本地 GeoJSON"));
geoMapLoads[mapId] = window.fetch(url, { credentials: "omit" }).then(function (response) {
if (!response.ok) throw new Error("地图资源加载失败(HTTP " + response.status + ")");
return response.json();
}).then(function (geoJson) {
if (!geoJson || !geoJson.type) throw new Error("地图资源不是有效 GeoJSON");
normalizeGeoJsonNames(geoJson);
geoMapCache[mapId] = geoJson;
return geoJson;
}).catch(function (error) {
delete geoMapLoads[mapId];
throw error;
});
return geoMapLoads[mapId];
}
function normalizeGeoJsonNames(geoJson) {
var features = geoJson && geoJson.features;
if (!features || !features.length) return;
for (var i = 0; i < features.length; i += 1) {
var feature = features[i] || {};
var properties = feature.properties || (feature.properties = {});
if (!properties.name) {
properties.name = properties.shapeName || properties.NAME_1 || properties.name_en || properties.id || "区域";
}
}
}
function geoRegions(data) {
var values = data.regions || [];
var output = [];
for (var i = 0; i < values.length; i += 1) {
var region = values[i];
if (!region || !region.name) continue;
var item = { name: String(region.name) };
if (isFinite(Number(region.value))) item.value = Number(region.value);
if (region.color) item.itemStyle = { areaColor: String(region.color) };
if (region.description) item.description = String(region.description);
if (region.selected === true) item.selected = true;
output.push(item);
}
return output;
}
function geoMarkers(data) {
var values = data.markers || [];
var output = [];
for (var i = 0; i < values.length; i += 1) {
var marker = values[i];
var coordinate = marker && geoPoint(marker.coordinate);
if (!coordinate) continue;
output.push({
name: marker.name || "地点", value: coordinate.concat([Number(marker.value) || 1]),
description: marker.description || "", itemStyle: marker.color ? { color: marker.color } : undefined
});
}
return output;
}
function geoLines(data) {
var values = data.lines || [];
var output = [];
for (var i = 0; i < values.length; i += 1) {
var line = values[i] || {};
var rawCoordinates = line.coordinates || [];
var coordinates = [];
for (var j = 0; j < rawCoordinates.length; j += 1) {
var coordinate = geoPoint(rawCoordinates[j]);
if (coordinate) coordinates.push(coordinate);
}
if (coordinates.length > 1) output.push({
name: line.name || "连接", coords: coordinates, description: line.description || "",
lineStyle: line.color ? { color: line.color } : undefined
});
}
return output;
}
function geoTooltip(params, data) {
var name = params.name || "地图区域";
var detail = geoDetail(data, name, params.data);
var description = detail.description || "";
return "<strong>" + escapeHtml(name) + "</strong>" +
(description ? "<br>" + escapeHtml(description) : "");
}
function geoDetail(data, name, itemData) {
var groups = [data.markers || [], data.regions || [], data.lines || []];
for (var groupIndex = 0; groupIndex < groups.length; groupIndex += 1) {
var group = groups[groupIndex];
for (var itemIndex = 0; itemIndex < group.length; itemIndex += 1) {
if (group[itemIndex] && group[itemIndex].name === name) return group[itemIndex];
}
}
return itemData || {};
}
function showGeoPopup(target, params, data) {
var name = params.name || "地图区域";
var detail = geoDetail(data, name, params.data);
var popup = target.querySelector(".df-geo-popup");
if (!popup) {
if (window.getComputedStyle && window.getComputedStyle(target).position === "static") {
target.style.position = "relative";
}
popup = document.createElement("section");
popup.className = "df-geo-popup";
popup.setAttribute("role", "dialog");
popup.setAttribute("aria-live", "polite");
popup.style.cssText = "position:absolute;z-index:10;max-width:280px;padding:16px 42px 16px 16px;border:1px solid #5aa8df;border-radius:12px;background:rgba(5,23,45,.96);box-shadow:0 16px 36px rgba(0,0,0,.34);color:#f8fbff;font:16px/1.55 -apple-system,BlinkMacSystemFont,'Segoe UI','Microsoft YaHei',sans-serif;";
target.appendChild(popup);
}
var description = detail.description || "暂无更多说明。";
var value = isFinite(Number(detail.value)) ? "<div style=\"margin-top:5px;color:#8fd8ff\">指标:" + escapeHtml(detail.value) + "</div>" : "";
popup.innerHTML = "<button type=\"button\" aria-label=\"关闭地图详情\" style=\"position:absolute;top:7px;right:9px;border:0;background:transparent;color:#b9dbec;font:700 20px/1 sans-serif;cursor:pointer\">×</button>" +
"<strong style=\"display:block;font-size:18px\">" + escapeHtml(name) + "</strong>" +
"<div style=\"margin-top:5px;color:#d9edf8\">" + escapeHtml(description) + "</div>" + value;
var close = popup.querySelector("button");
if (close) close.onclick = function () { popup.style.display = "none"; };
var rawX = params.event && Number(params.event.offsetX);
var rawY = params.event && Number(params.event.offsetY);
var x = isFinite(rawX) ? rawX : target.clientWidth / 2;
var y = isFinite(rawY) ? rawY : target.clientHeight / 2;
popup.style.left = Math.max(12, Math.min(Math.max(12, target.clientWidth - 292), x + 14)) + "px";
popup.style.top = Math.max(12, Math.min(Math.max(12, target.clientHeight - 150), y + 14)) + "px";
popup.style.display = "block";
}
function initGeoMaps() {
var items = blocks("data-df-geo");
for (var i = 0; i < items.length; i += 1) {
var block = items[i];
var target = document.getElementById(block.getAttribute("data-df-geo"));
if (!target) continue;
var data = parse(block, target);
if (!data) continue;
if (!window.echarts) {
showError(target, "ECharts 运行库加载失败");
continue;
}
(function (mapTarget, mapData) {
size(mapTarget);
whenVisible(mapTarget, function () {
var mapId = mapData.map || "china-provinces";
loadGeoMap(mapId).then(function (geoJson) {
mapTarget.setAttribute("role", "application");
mapTarget.setAttribute("aria-label", mapData.title || "交互地图");
window.echarts.registerMap(mapId, geoJson);
var background = mapData.mode === "background";
var labels = mapData.showLabels === true || (!background && mapData.showLabels !== false);
var areaColor = mapData.areaColor || (background ? "#123b67" : "#d8ecff");
var borderColor = mapData.borderColor || (background ? "#4e8cbf" : "#5d8cbb");
var emphasisColor = mapData.emphasisColor || (background ? "#2578bc" : "#8ec5f0");
var regions = geoRegions(mapData);
var defaultView = geoMapDefaultViews[mapId] || {};
var mapCenter = geoPoint(mapData.center) || defaultView.center;
var configuredZoom = Number(mapData.zoom);
var mapZoom = isFinite(configuredZoom) && configuredZoom > 0 ? configuredZoom : defaultView.zoom;
var chart = window.echarts.init(mapTarget, null, { renderer: "canvas" });
chart.setOption({
animation: false,
backgroundColor: "transparent",
tooltip: {
trigger: "item", backgroundColor: "rgba(8, 22, 45, 0.94)", borderColor: "#5aa8df",
textStyle: { color: "#f8fbff", fontSize: 16 },
formatter: function (params) { return geoTooltip(params, mapData); }
},
geo: {
map: mapId, roam: mapData.roam !== false,
layoutCenter: mapData.layoutCenter || ["50%", "50%"],
layoutSize: mapData.layoutSize || (background ? "118%" : "100%"),
center: mapCenter, zoom: mapZoom,
label: { show: labels, color: background ? "#d7ecff" : "#183450", fontSize: 16 },
itemStyle: { areaColor: areaColor, borderColor: borderColor, borderWidth: 1.25 },
emphasis: { label: { show: labels }, itemStyle: { areaColor: emphasisColor } },
select: { label: { show: labels }, itemStyle: { areaColor: mapData.selectColor || "#1e6cac" } }
},
series: [
{
type: "map", map: mapId, geoIndex: 0, data: regions, selectedMode: "single", z: 1,
itemStyle: { areaColor: areaColor, borderColor: borderColor, borderWidth: 1.25 },
emphasis: { label: { show: labels }, itemStyle: { areaColor: emphasisColor } },
select: { label: { show: labels }, itemStyle: { areaColor: mapData.selectColor || "#1e6cac" } }
},
{
type: "effectScatter", coordinateSystem: "geo", z: 4, data: geoMarkers(mapData),
symbolSize: mapData.markerSize || 13, showEffectOn: "render", rippleEffect: { scale: 3.2, brushType: "stroke" },
label: { show: true, formatter: "{b}", position: "right", color: background ? "#f8fbff" : "#183450", fontSize: 16, fontWeight: 700 },
itemStyle: { color: mapData.markerColor || "#f4bd57", shadowBlur: 12, shadowColor: "rgba(244,189,87,0.55)" }
},
{
type: "lines", coordinateSystem: "geo", z: 3, data: geoLines(mapData),
lineStyle: { color: mapData.lineColor || "#77d4ff", width: 2, opacity: 0.82, curveness: 0.16 },
effect: { show: true, period: 5, symbol: "arrow", symbolSize: 8, trailLength: 0.08 }
}
]
});
chart.on("click", function (params) {
var detail = geoDetail(mapData, params.name || "", params.data);
if (mapData.popup !== false) showGeoPopup(mapTarget, params, mapData);
if (!window.CustomEvent) return;
window.dispatchEvent(new window.CustomEvent("deerflow:geo-map-click", {
detail: {
targetId: mapTarget.id, name: params.name || "", kind: params.seriesType || "region",
data: detail, description: detail.description || ""
}
}));
});
bindEChartsResize(mapTarget, chart);
}).catch(function (error) {
showError(mapTarget, "地图渲染失败:" + (error && error.message ? error.message : "请检查地图数据"));
});
});
})(target, data);
}
}
function initMaps() {
var items = blocks("data-df-map");
for (var i = 0; i < items.length; i += 1) {
var block = items[i];
var target = document.getElementById(block.getAttribute("data-df-map"));
if (!target) continue;
var data = parse(block, target);
if (!data) continue;
if (!window.L) {
showError(target, "Leaflet 运行库加载失败");
continue;
}
size(target);
(function (mapTarget, mapData) {
whenVisible(mapTarget, function () {
try {
mapTarget.setAttribute("role", "region");
mapTarget.setAttribute("aria-label", mapData.title || "地图");
var map = window.L.map(mapTarget, { zoomControl: true });
var config = window.DEERFLOW_HTML_RUNTIME_CONFIG || {};
var tileUrl = config.mapTileUrl;
if (tileUrl) {
var tileErrors = 0;
window.L.tileLayer(tileUrl, { maxZoom: 20, attribution: /google/i.test(tileUrl) ? "Google" : "" })
.on("tileerror", function () {
tileErrors += 1;
if (tileErrors === 3) showError(mapTarget, "地图瓦片加载失败;点、线、面数据仍可查看");
})
.addTo(map);
}
var bounds = [];
var markers = mapData.markers || [];
for (var m = 0; m < markers.length; m += 1) {
var marker = markers[m];
var markerPoint = leafletPoint(marker.coordinate);
if (!markerPoint) continue;
bounds.push(markerPoint);
var dot = window.L.circleMarker(markerPoint, {
radius: 7, color: "#1d4ed8", weight: 2, fillColor: "#3b82f6", fillOpacity: 0.9
}).addTo(map);
dot.bindPopup("<strong>" + escapeHtml(marker.name || "地点") + "</strong>" +
(marker.description ? "<br>" + escapeHtml(marker.description) : ""));
dot.bindTooltip(escapeHtml(marker.name || "地点"));
}
var lines = mapData.lines || [];
for (var l = 0; l < lines.length; l += 1) {
var linePoints = [];
var rawLine = lines[l].coordinates || [];
for (var p = 0; p < rawLine.length; p += 1) {
var linePoint = leafletPoint(rawLine[p]);
if (linePoint) { linePoints.push(linePoint); bounds.push(linePoint); }
}
if (linePoints.length > 1) window.L.polyline(linePoints, { color: "#2563eb", weight: 3, opacity: 0.85 }).addTo(map);
}
var polygons = mapData.polygons || [];
for (var g = 0; g < polygons.length; g += 1) {
var polygonPoints = [];
var rawPolygon = polygons[g].coordinates || [];
for (var q = 0; q < rawPolygon.length; q += 1) {
var polygonPoint = leafletPoint(rawPolygon[q]);
if (polygonPoint) { polygonPoints.push(polygonPoint); bounds.push(polygonPoint); }
}
if (polygonPoints.length > 2) window.L.polygon(polygonPoints, {
color: "#7c3aed", weight: 2, fillColor: "#8b5cf6", fillOpacity: 0.2
}).addTo(map);
}
var center = leafletPoint(mapData.center);
if (center) map.setView(center, Number(mapData.zoom) || 6);
else if (bounds.length) map.fitBounds(bounds, { padding: [28, 28], maxZoom: Number(mapData.zoom) || 10 });
else map.setView([35.8617, 104.1954], Number(mapData.zoom) || 4);
bindCanvasResize(mapTarget, function () { map.invalidateSize(); });
} catch (error) {
showError(mapTarget, "地图渲染失败:" + (error && error.message ? error.message : "请检查地图数据"));
}
});
})(target, data);
}
}
function initFlows() {
var items = blocks("data-df-flow");
for (var i = 0; i < items.length; i += 1) {
var block = items[i];
var target = document.getElementById(block.getAttribute("data-df-flow"));
if (!target) continue;
var data = parse(block, target);
if (!data) continue;
if (!window.G6 || !window.G6.Graph) {
showError(target, "AntV G6 运行库加载失败");
continue;
}
size(target);
(function (flowTarget, flowData) {
whenVisible(flowTarget, function () {
try {
var nodes = flowData.nodes || [];
var edges = flowData.edges || [];
var rankDirection = flowData.direction === "TB" ? "TB" : "LR";
var colors = {
start: ["#dcfce7", "#22c55e", "#14532d"], end: ["#ede9fe", "#8b5cf6", "#4c1d95"],
decision: ["#fef3c7", "#f59e0b", "#78350f"], risk: ["#fee2e2", "#ef4444", "#7f1d1d"],
process: ["#dbeafe", "#3b82f6", "#1e3a8a"], stage: ["#e0e7ff", "#6366f1", "#312e81"],
task: ["#f1f5f9", "#94a3b8", "#1e293b"]
};
var typeNames = { start: "开始", end: "结束", decision: "判断", risk: "异常", process: "处理", stage: "阶段", task: "任务" };
var graphData = { nodes: [], edges: [] };
for (var n = 0; n < nodes.length; n += 1) {
var node = nodes[n];
var pair = colors[node.type] || colors.task;
graphData.nodes.push({ id: String(node.id), data: {
label: node.label || node.id, description: node.description || "",
typeName: typeNames[node.type] || typeNames.task,
fill: node.color || pair[0], stroke: pair[1], textColor: pair[2], terminal: node.type === "start" || node.type === "end"
}});
}
for (var e = 0; e < edges.length; e += 1) {
graphData.edges.push({ source: String(edges[e].source), target: String(edges[e].target), data: { label: edges[e].label || "" } });
}
flowTarget.setAttribute("role", "img");
flowTarget.setAttribute("aria-label", (flowData.title || "流程图") + ",包含 " + nodes.length + " 个步骤和 " + edges.length + " 条连线");
var graph = new window.G6.Graph({
container: flowTarget, data: graphData, autoFit: "view", padding: 72, zoomRange: [0.1, 2], animation: false,
layout: { type: "dagre", rankdir: rankDirection, nodesep: 44, ranksep: 60 },
node: { type: "rect", style: {
size: function (d) {
var width = Math.max(190, Math.min(260, 118 + String(d.data.label || "").length * 15));
return [width, 68];
},
radius: function (d) { return d.data.terminal ? 38 : 16; },
opacity: 1, fillOpacity: 1, strokeOpacity: 1,
fill: function (d) { return d.data.fill; }, stroke: function (d) { return d.data.stroke; }, lineWidth: 2.5,
shadowColor: "rgba(15,23,42,0.10)", shadowBlur: 12, shadowOffsetY: 4,
labelText: function (d) { return d.data.typeName + " · " + d.data.label; },
labelPlacement: "center", labelFill: function (d) { return d.data.textColor; }, labelOpacity: 1, labelFontSize: 16, labelFontWeight: 700,
labelWordWrap: true, labelMaxWidth: 220, cursor: "grab"
}},
edge: { type: "polyline", style: {
opacity: 1, strokeOpacity: 1, stroke: "#94a3b8", lineWidth: 2.25, radius: 16, endArrow: true,
labelText: function (d) { return d.data.label; }, labelFill: "#334155", labelOpacity: 1, labelFontSize: 12,
labelBackground: true, labelBackgroundFill: "#ffffff", labelBackgroundRadius: 6, labelPadding: [4, 7]
}},
behaviors: ["drag-canvas", "zoom-canvas", "drag-element"]
});
var rendered = graph.render();
if (rendered && rendered.catch) rendered.catch(function (error) { showError(flowTarget, "流程图渲染失败:" + error.message); });
bindCanvasResize(flowTarget, function () {
if (graph.resize) graph.resize();
if (graph.fitView) graph.fitView();
});
} catch (error) {
showError(flowTarget, "流程图渲染失败:" + (error && error.message ? error.message : "请检查流程数据"));
}
});
})(target, data);
}
}
ready(function () {
initFlows();
initCharts();
initGeoMaps();
initMaps();
});
})();