fix: 优化指引页面复制与流程图布局

This commit is contained in:
2026-04-24 14:18:33 +08:00
parent e935afddfe
commit 0e73a77b86
2 changed files with 197 additions and 6 deletions

View File

@@ -51,6 +51,18 @@ class ManageGuidePages extends Page
}
}
$pageMap = $pages->keyBy('id');
$incomingEdges = [];
$outgoingEdges = [];
foreach ($pages as $p) {
$incomingEdges[$p->id] = [];
$outgoingEdges[$p->id] = [];
}
foreach ($edgeModels as $e) {
$incomingEdges[$e->to_page_id][] = $e;
$outgoingEdges[$e->from_page_id][] = $e;
}
// BFS from entry nodes (no incoming edges) to assign levels
$hasIncoming = array_flip($edgeModels->pluck('to_page_id')->toArray());
$levels = [];
@@ -91,11 +103,80 @@ class ManageGuidePages extends Page
}
ksort($levelGroups);
$orders = [];
foreach ($levelGroups as $ids) {
foreach (array_values($ids) as $index => $id) {
$orders[$id] = $index;
}
}
$edgeOffset = function (GuidePageEdge $edge) use ($pageMap): float {
$page = $pageMap->get($edge->from_page_id);
$options = $page?->options ?? [];
$index = $edge->label === null ? false : array_search($edge->label, $options, true);
return $index === false ? 0 : (($index + 1) / (count($options) + 1)) * 0.4;
};
$incomingScore = function (int $id) use (&$orders, $incomingEdges, $levels, $edgeOffset): ?float {
$scores = [];
foreach ($incomingEdges[$id] ?? [] as $edge) {
if (($levels[$edge->from_page_id] ?? null) >= ($levels[$id] ?? null)) {
continue;
}
$scores[] = ($orders[$edge->from_page_id] ?? 0) + $edgeOffset($edge);
}
return empty($scores) ? null : array_sum($scores) / count($scores);
};
$outgoingScore = function (int $id) use (&$orders, $outgoingEdges, $levels): ?float {
$scores = [];
foreach ($outgoingEdges[$id] ?? [] as $edge) {
if (($levels[$edge->to_page_id] ?? null) <= ($levels[$id] ?? null)) {
continue;
}
$scores[] = $orders[$edge->to_page_id] ?? 0;
}
return empty($scores) ? null : array_sum($scores) / count($scores);
};
$sortLevel = function (array &$ids, callable $scoreResolver) use (&$orders): void {
usort($ids, function (int $a, int $b) use ($scoreResolver, $orders): int {
$scoreA = $scoreResolver($a) ?? ($orders[$a] ?? 0);
$scoreB = $scoreResolver($b) ?? ($orders[$b] ?? 0);
return $scoreA <=> $scoreB ?: ($orders[$a] ?? 0) <=> ($orders[$b] ?? 0) ?: $a <=> $b;
});
foreach ($ids as $index => $id) {
$orders[$id] = $index;
}
};
for ($i = 0; $i < 3; $i++) {
foreach ($levelGroups as $level => &$ids) {
if ($level === array_key_first($levelGroups)) {
continue;
}
$sortLevel($ids, $incomingScore);
}
unset($ids);
foreach (array_reverse(array_keys($levelGroups)) as $level) {
if ($level === array_key_last($levelGroups)) {
continue;
}
$sortLevel($levelGroups[$level], $outgoingScore);
}
}
// Compute positions: center each level vertically, stack horizontally (left-to-right)
$nodeWidth = 180; // matches CSS max-width
$nodeHeight = 80; // compact node height
$gapX = 110; // horizontal gap between levels
$gapY = 30; // vertical gap within same level
$gapY = 60; // vertical gap within same level
$positions = [];
foreach ($levelGroups as $level => $ids) {
@@ -229,6 +310,29 @@ class ManageGuidePages extends Page
});
}
public function copyPageAction(): Action
{
return Action::make('copyPage')
->label('复制页面')
->icon('heroicon-o-document-duplicate')
->requiresConfirmation()
->modalHeading('复制页面')
->modalDescription('确认复制该页面?复制后会生成一个独立的新页面,不会复制连线关系。')
->modalSubmitActionLabel('确认复制')
->action(function (array $arguments): void {
$page = $this->getRecord()->pages()->findOrFail($arguments['id']);
$this->getRecord()->pages()->create([
'title' => $page->title.' - 副本',
'content' => $page->content,
'options' => $page->options ?? [],
]);
$this->loadGraph();
$this->dispatchGraphUpdated();
});
}
public function deletePageAction(): Action
{
return Action::make('deletePage')