fix: tree & guide

This commit is contained in:
2026-03-24 09:21:21 +08:00
parent b74ba1a3f8
commit 42a879e961
15 changed files with 2619 additions and 601 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Guide;
use App\Models\GuidePage;
use App\Models\GuidePageEdge;
use App\Services\KnowledgeContextService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -90,7 +91,7 @@ class TerminalApiController extends Controller
/**
* POST /api/terminal/guides/pages
* 组合多个指引页面,返回递归树形结构
* 返回指引页面(状态机格式,每页带 next 指针)
*/
public function guidePages(Request $request): JsonResponse
{
@@ -102,104 +103,54 @@ class TerminalApiController extends Controller
$terminal = $request->attributes->get('terminal');
$accessibleIds = $this->getTerminalGuides($terminal)->pluck('guides.id')->toArray();
$guideIds = $request->input('guide_ids');
$pages = [];
$guideIds = collect($request->input('guide_ids'))
->intersect($accessibleIds)
->values()
->toArray();
foreach ($guideIds as $guideId) {
if (!in_array($guideId, $accessibleIds)) {
continue;
$pages = GuidePage::whereIn('guide_id', $guideIds)->get();
$edges = GuidePageEdge::whereIn('guide_id', $guideIds)
->orderBy('from_page_id')
->orderBy('sort')
->get();
$edgesByFrom = $edges->groupBy('from_page_id');
$hasIncoming = $edges->pluck('to_page_id')->unique()->flip();
$guides = [];
foreach ($pages->groupBy('guide_id') as $guideId => $guidePages) {
$entryPage = $guidePages->first(fn($p) => !$hasIncoming->has($p->id));
$pagesMap = [];
foreach ($guidePages as $page) {
$next = $edgesByFrom->get($page->id, collect())
->map(function (GuidePageEdge $e) {
$item = ['page_id' => $e->to_page_id];
if ($e->label !== null) {
$item['label'] = $e->label;
}
return $item;
})->values()->toArray();
$pagesMap[$page->id] = [
'id' => $page->id,
'title' => $page->title,
'html_url' => $page->html_url,
'next' => $next,
];
}
$guide = Guide::with(
$this->buildEagerLoadArray('trunkPages', 5)
)->find($guideId);
if (!$guide) {
continue;
}
foreach ($guide->trunkPages as $page) {
$pages = array_merge($pages, $this->flattenSequentialPages($page, $guide->name, $guide->id));
}
$guides[$guideId] = [
'entry_page_id' => $entryPage?->id,
'pages' => $pagesMap,
];
}
return response()->json([
'pages' => $pages,
'total_pages' => count($pages),
'guides' => $guides,
]);
}
/**
* 将树形页面结构展平:顺序节点(无 options平铺分支节点保留嵌套
*/
private function flattenSequentialPages(GuidePage $page, string $guideName, int $guideId): array
{
$data = [
'id' => $page->id,
'guide_id' => $guideId,
'guide_name' => $guideName,
'page_number' => $page->page_number,
'title' => $page->title,
'html_url' => $page->html_url,
];
if ($page->options && $page->branchChildren->isNotEmpty()) {
$data['options'] = $page->options;
$branches = [];
foreach ($page->branchChildren as $child) {
$branches[$child->branch_option][] =
$this->buildPageTree($child, $guideName, $guideId);
}
$data['branches'] = $branches;
return [$data];
}
if ($page->branchChildren->isNotEmpty()) {
$result = [$data];
foreach ($page->branchChildren as $child) {
$result = array_merge($result, $this->flattenSequentialPages($child, $guideName, $guideId));
}
return $result;
}
return [$data];
}
private function buildPageTree(GuidePage $page, string $guideName, int $guideId): array
{
$data = [
'id' => $page->id,
'guide_id' => $guideId,
'guide_name' => $guideName,
'page_number' => $page->page_number,
'title' => $page->title,
'html_url' => $page->html_url,
];
if ($page->options && $page->branchChildren->isNotEmpty()) {
$data['options'] = $page->options;
$branches = [];
foreach ($page->branchChildren as $child) {
$branches[$child->branch_option][] =
$this->buildPageTree($child, $guideName, $guideId);
}
$data['branches'] = $branches;
}
return $data;
}
private function buildEagerLoadArray(string $base, int $depth): array
{
$loads = [$base => fn($q) => $q->orderBy('sort_order')];
$current = $base;
for ($i = 0; $i < $depth; $i++) {
$current .= '.branchChildren';
$loads[$current] = fn($q) => $q->orderBy('sort_order');
}
return $loads;
}
/**
* 获取终端可见的指引(线站关联 + 全局)
*/
@@ -210,7 +161,7 @@ class TerminalApiController extends Controller
return Guide::published()->where(function ($q) use ($stationId) {
$q->whereDoesntHave('stations'); // 全局指引
if ($stationId) {
$q->orWhereHas('stations', fn ($sq) => $sq->where('stations.id', $stationId));
$q->orWhereHas('stations', fn($sq) => $sq->where('stations.id', $stationId));
}
});
}