AnthologyController.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. namespace App\Http\Controllers\Library;
  3. use App\Http\Controllers\Controller;
  4. use App\Services\CollectionService;
  5. use Carbon\Carbon;
  6. use Illuminate\Http\Request;
  7. use Illuminate\Pagination\LengthAwarePaginator;
  8. class AnthologyController extends Controller
  9. {
  10. // 封面渐变色池:uid 首字节取余循环,保证同一文集颜色稳定
  11. private array $coverGradients = [
  12. 'linear-gradient(160deg, #2d2010,rgb(94, 60, 20))',
  13. 'linear-gradient(160deg, #1a2d10,rgb(64, 105, 34))',
  14. 'linear-gradient(160deg, #0d1f3c,rgb(31, 64, 112))',
  15. 'linear-gradient(160deg, #2d1020,rgb(109, 35, 71))',
  16. 'linear-gradient(160deg, #1a1a2d,rgb(48, 48, 92))',
  17. 'linear-gradient(160deg, #1a2820,rgb(53, 80, 66))',
  18. ];
  19. // 作者色池:同上,根据 studio.id 首字节取余
  20. private array $authorColors = [
  21. '#c8860a',
  22. '#2e7d32',
  23. '#1565c0',
  24. '#6a1b9a',
  25. '#c62828',
  26. '#00695c',
  27. '#4e342e',
  28. '#37474f',
  29. ];
  30. public function __construct(private CollectionService $collectionService) {}
  31. // -------------------------------------------------------------------------
  32. // 从 uid / id 字符串中提取一个稳定的整数,用于色池取余
  33. // -------------------------------------------------------------------------
  34. private function colorIndex(string $uid): int
  35. {
  36. return hexdec(substr(str_replace('-', '', $uid), 0, 4)) % 255;
  37. }
  38. // -------------------------------------------------------------------------
  39. // 将 studio 对象转换为 blade 所需的 author 数组
  40. // -------------------------------------------------------------------------
  41. private function formatAuthor(array $studio): array
  42. {
  43. $name = $studio['nickName'] ?? $studio['studioName'] ?? '未知';
  44. $initials = mb_substr($name, 0, 2);
  45. $colorIdx = $this->colorIndex($studio['id'] ?? '0');
  46. return [
  47. 'name' => $name,
  48. 'initials' => $initials,
  49. 'color' => $this->authorColors[$colorIdx % count($this->authorColors)],
  50. 'avatar' => $studio['avatar'] ?? null,
  51. ];
  52. }
  53. // -------------------------------------------------------------------------
  54. // 将 CollectionResource 单条转换为 index 卡片所需格式
  55. // -------------------------------------------------------------------------
  56. private function formatForCard(array $item, int $index): array
  57. {
  58. $uid = $item['uid'];
  59. $colorIdx = $this->colorIndex($uid);
  60. // article_list => 章节标题列表
  61. $chapters = collect($item['article_list'] ?? [])
  62. ->pluck('title')
  63. ->toArray();
  64. return [
  65. 'id' => $uid,
  66. 'title' => $item['title'],
  67. 'subtitle' => $item['subtitle'] ?? null,
  68. 'description' => $item['summary'] ?? null,
  69. 'cover_image' => null, // 暂无封面图字段,留空走渐变
  70. 'cover_gradient' => $this->coverGradients[$colorIdx % count($this->coverGradients)],
  71. 'author' => $this->formatAuthor($item['studio'] ?? []),
  72. 'chapters' => $chapters,
  73. 'children_number' => $item['childrenNumber'] ?? count($chapters),
  74. 'updated_at' => isset($item['updated_at'])
  75. ? Carbon::parse($item['updated_at'])->format('Y-m-d')
  76. : '',
  77. ];
  78. }
  79. // =========================================================================
  80. // index
  81. // =========================================================================
  82. public function index(Request $request)
  83. {
  84. $perPage = 10;
  85. $currentPage = (int) $request->input('page', 1);
  86. $result = $this->collectionService->getPublicList($perPage, $currentPage);
  87. // $result['data'] 是 CollectionResource collection,转为数组逐条加工
  88. $items = collect($result['data'])
  89. ->values()
  90. ->map(fn ($item, $i) => $this->formatForCard(
  91. is_array($item) ? $item : $item->toArray(request()),
  92. $i
  93. ));
  94. $total = $result['total'];
  95. $paginator = new LengthAwarePaginator(
  96. $items,
  97. $total,
  98. $perPage,
  99. $currentPage,
  100. ['path' => $request->url(), 'query' => $request->query()]
  101. );
  102. // 侧边栏作者列表:从当页数据聚合(如需完整列表可单独查询)
  103. $authors = $items
  104. ->groupBy(fn ($i) => $i['author']['name'])
  105. ->map(fn ($group, $name) => [
  106. 'name' => $name,
  107. 'initials' => $group->first()['author']['initials'],
  108. 'color' => $group->first()['author']['color'],
  109. 'avatar' => $group->first()['author']['avatar'],
  110. 'count' => $group->count(),
  111. ])
  112. ->values();
  113. return view('library.anthology.index', [
  114. 'anthologies' => $paginator,
  115. 'authors' => $authors,
  116. 'total' => $total,
  117. ]);
  118. }
  119. // =========================================================================
  120. // show
  121. // =========================================================================
  122. public function show(string $uid)
  123. {
  124. $result = $this->collectionService->getCollection($uid);
  125. if (isset($result['error'])) {
  126. abort($result['code'] ?? 404, $result['error']);
  127. }
  128. $raw = is_array($result['data'])
  129. ? $result['data']
  130. : $result['data']->toArray(request());
  131. $colorIdx = $this->colorIndex($raw['uid']);
  132. $author = $this->formatAuthor($raw['studio'] ?? []);
  133. // 只保留 level=1 的顶级章节
  134. $articles = collect($raw['article_list'] ?? [])
  135. ->filter(fn ($a) => ($a['level'] ?? 1) === 1)
  136. ->values()
  137. ->map(fn ($a, $i) => [
  138. 'id' => $a['article_id'],
  139. 'order' => $i + 1,
  140. 'title' => $a['title'],
  141. ])
  142. ->toArray();
  143. $anthology = [
  144. 'id' => $raw['uid'],
  145. 'title' => $raw['title'],
  146. 'subtitle' => $raw['subtitle'] ?? null,
  147. 'description' => $raw['summary'] ?? $raw['subtitle'] ?? null,
  148. 'about' => $raw['summary'] ?? null,
  149. 'cover_image' => null,
  150. 'cover_gradient' => $this->coverGradients[$colorIdx % count($this->coverGradients)],
  151. 'tags' => array_filter([$raw['lang'] ?? null]),
  152. 'language' => $raw['lang'] ?? null,
  153. 'category' => null,
  154. 'created_at' => isset($raw['created_at'])
  155. ? Carbon::parse($raw['created_at'])->format('Y-m-d')
  156. : '',
  157. 'updated_at' => isset($raw['updated_at'])
  158. ? Carbon::parse($raw['updated_at'])->format('Y-m-d')
  159. : '',
  160. 'children_number' => $raw['childrenNumber'] ?? 0,
  161. 'author' => array_merge($author, [
  162. 'bio' => null,
  163. 'article_count' => $raw['childrenNumber'] ?? 0,
  164. ]),
  165. 'articles' => $articles,
  166. ];
  167. // 相关文集:同作者其他文集
  168. $relatedResult = $this->collectionService->getPublicList(20, 1);
  169. $related = collect($relatedResult['data'])
  170. ->map(fn ($item) => is_array($item) ? $item : $item->toArray(request()))
  171. ->filter(
  172. fn ($item) => $item['uid'] !== $uid &&
  173. ($item['studio']['id'] ?? '') === ($raw['studio']['id'] ?? '')
  174. )
  175. ->take(3)
  176. ->map(fn ($item) => [
  177. 'id' => $item['uid'],
  178. 'title' => $item['title'],
  179. 'author_name' => $item['studio']['nickName'] ?? $item['studio']['studioName'] ?? '',
  180. 'cover_gradient' => $this->coverGradients[$this->colorIndex($item['uid']) % count($this->coverGradients)],
  181. ])
  182. ->values();
  183. return view('library.anthology.show', [
  184. 'anthology' => $anthology,
  185. 'related' => $related,
  186. ]);
  187. }
  188. }