KnowledgeNetworkComponent.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <?php
  2. namespace App\Livewire\Integrations;
  3. use Livewire\Component;
  4. use App\Services\KnowledgeServiceApi;
  5. use App\Services\QuestionBankService;
  6. use Illuminate\Support\Facades\Http;
  7. class KnowledgeNetworkComponent extends Component
  8. {
  9. public $selectedKpCode = null;
  10. public $networkData = [];
  11. public $kpRelations = [];
  12. public $questionRelations = [];
  13. public $isLoading = false;
  14. protected $listeners = [
  15. 'kpSelected' => 'handleKpSelected',
  16. 'clearSelection' => 'clearSelection',
  17. 'refreshNetwork' => 'loadNetworkData',
  18. ];
  19. public function mount($selectedKpCode = null)
  20. {
  21. $this->selectedKpCode = $selectedKpCode;
  22. $this->loadNetworkData();
  23. }
  24. public function loadNetworkData()
  25. {
  26. $this->isLoading = true;
  27. try {
  28. // 获取知识图谱关联关系
  29. $this->loadKpRelations();
  30. // 获取题库关联关系
  31. $this->loadQuestionRelations();
  32. // 整合数据
  33. $this->buildNetworkData();
  34. } catch (\Exception $e) {
  35. \Log::error('加载知识网络失败', [
  36. 'kp_code' => $this->selectedKpCode,
  37. 'error' => $e->getMessage()
  38. ]);
  39. $this->dispatch('error', message: '加载知识网络失败');
  40. }
  41. $this->isLoading = false;
  42. }
  43. private function loadKpRelations()
  44. {
  45. try {
  46. $service = app(KnowledgeServiceApi::class);
  47. if ($this->selectedKpCode) {
  48. // 获取选中知识点的关联关系
  49. $upstream = $service->getUpstreamNodes($this->selectedKpCode, 3);
  50. $downstream = $service->getDownstreamNodes($this->selectedKpCode, 3);
  51. $related = $service->getRelatedNodes($this->selectedKpCode);
  52. $this->kpRelations = [
  53. 'upstream' => $upstream['nodes'] ?? [],
  54. 'downstream' => $downstream['nodes'] ?? [],
  55. 'related' => $related['nodes'] ?? [],
  56. ];
  57. } else {
  58. // 获取所有关联关系(从完整图谱中提取)
  59. $graphData = $this->getFullGraphData();
  60. $this->kpRelations = $graphData;
  61. }
  62. } catch (\Exception $e) {
  63. \Log::error('获取知识关联关系失败', ['error' => $e->getMessage()]);
  64. }
  65. }
  66. private function loadQuestionRelations()
  67. {
  68. try {
  69. $service = app(QuestionBankService::class);
  70. // 获取所有题库统计数据
  71. $stats = $service->getKnowledgePointStatistics();
  72. if ($this->selectedKpCode) {
  73. // 只返回选中知识点的统计
  74. $selectedStats = collect($stats)->firstWhere('kp_code', $this->selectedKpCode);
  75. $this->questionRelations = $selectedStats ? [$selectedStats] : [];
  76. } else {
  77. $this->questionRelations = $stats;
  78. }
  79. } catch (\Exception $e) {
  80. \Log::error('获取题库关联关系失败', ['error' => $e->getMessage()]);
  81. }
  82. }
  83. private function getFullGraphData()
  84. {
  85. try {
  86. $knowledgeApiBase = config('services.knowledge_api.base_url', 'http://localhost:5011');
  87. $response = Http::timeout(10)
  88. ->get($knowledgeApiBase . '/graph/export');
  89. if ($response->successful()) {
  90. return $response->json();
  91. }
  92. } catch (\Exception $e) {
  93. \Log::error('获取完整图谱失败', ['error' => $e->getMessage()]);
  94. }
  95. return ['nodes' => [], 'edges' => []];
  96. }
  97. private function buildNetworkData()
  98. {
  99. $network = [
  100. 'nodes' => [],
  101. 'links' => [],
  102. 'groups' => [
  103. 'knowledge' => ['name' => '知识点', 'color' => '#3B82F6'],
  104. 'questions' => ['name' => '题库', 'color' => '#10B981'],
  105. 'skills' => ['name' => '技能', 'color' => '#F59E0B'],
  106. ],
  107. ];
  108. // 添加知识点节点
  109. $nodesMap = [];
  110. if ($this->selectedKpCode) {
  111. // 选中模式:添加相关知识点
  112. $relatedNodes = array_merge(
  113. $this->kpRelations['upstream'] ?? [],
  114. $this->kpRelations['downstream'] ?? [],
  115. $this->kpRelations['related'] ?? []
  116. );
  117. foreach ($relatedNodes as $node) {
  118. $kpCode = $node['kp_code'];
  119. $nodesMap[$kpCode] = [
  120. 'id' => $kpCode,
  121. 'name' => $node['cn_name'] ?? $kpCode,
  122. 'group' => 'knowledge',
  123. 'type' => 'knowledge_point',
  124. 'data' => $node,
  125. ];
  126. }
  127. // 添加选中的知识点
  128. $selectedNode = collect($this->kpRelations['upstream'] ?? [])
  129. ->merge($this->kpRelations['downstream'] ?? [])
  130. ->merge($this->kpRelations['related'] ?? [])
  131. ->firstWhere('kp_code', $this->selectedKpCode);
  132. if ($selectedNode) {
  133. $nodesMap[$this->selectedKpCode] = [
  134. 'id' => $this->selectedKpCode,
  135. 'name' => $selectedNode['cn_name'] ?? $this->selectedKpCode,
  136. 'group' => 'knowledge',
  137. 'type' => 'knowledge_point',
  138. 'data' => $selectedNode,
  139. ];
  140. }
  141. } else {
  142. // 完整模式:从题库统计中获取知识点
  143. foreach ($this->questionRelations as $stat) {
  144. $kpCode = $stat['kp_code'];
  145. if (!$kpCode) continue;
  146. $nodesMap[$kpCode] = [
  147. 'id' => $kpCode,
  148. 'name' => $stat['cn_name'] ?? $kpCode,
  149. 'group' => 'knowledge',
  150. 'type' => 'knowledge_point',
  151. 'question_count' => $stat['total_questions'] ?? 0,
  152. 'data' => $stat,
  153. ];
  154. }
  155. }
  156. // 添加技能节点
  157. foreach ($this->questionRelations as $stat) {
  158. if (isset($stat['skills']) && is_array($stat['skills'])) {
  159. foreach ($stat['skills'] as $skill) {
  160. $skillCode = $skill['skill_code'] ?? '';
  161. if (!$skillCode) continue;
  162. $nodesMap[$skillCode] = [
  163. 'id' => $skillCode,
  164. 'name' => $skillCode,
  165. 'group' => 'skills',
  166. 'type' => 'skill',
  167. 'data' => $skill,
  168. ];
  169. }
  170. }
  171. }
  172. // 添加链接
  173. foreach ($this->questionRelations as $stat) {
  174. $kpCode = $stat['kp_code'];
  175. if (!$kpCode) continue;
  176. // 知识点到题目的链接
  177. if (isset($nodesMap[$kpCode])) {
  178. $questionCount = $stat['total_questions'] ?? 0;
  179. if ($questionCount > 0) {
  180. $network['links'][] = [
  181. 'source' => $kpCode,
  182. 'target' => "questions_{$kpCode}",
  183. 'value' => $questionCount,
  184. 'type' => 'has_questions',
  185. ];
  186. }
  187. // 知识点到技能点的链接
  188. if (isset($stat['skills']) && is_array($stat['skills'])) {
  189. foreach ($stat['skills'] as $skill) {
  190. $skillCode = $skill['skill_code'] ?? '';
  191. if ($skillCode && isset($nodesMap[$skillCode])) {
  192. $network['links'][] = [
  193. 'source' => $kpCode,
  194. 'target' => $skillCode,
  195. 'value' => $skill['question_count'] ?? 1,
  196. 'type' => 'has_skill',
  197. ];
  198. }
  199. }
  200. }
  201. }
  202. }
  203. $network['nodes'] = array_values($nodesMap);
  204. $this->networkData = $network;
  205. }
  206. public function handleKpSelected($kpCode)
  207. {
  208. $this->selectedKpCode = $kpCode;
  209. $this->loadNetworkData();
  210. }
  211. public function clearSelection()
  212. {
  213. $this->selectedKpCode = null;
  214. $this->loadNetworkData();
  215. }
  216. public function render()
  217. {
  218. return view('livewire.integrations.knowledge-network-component', [
  219. 'networkData' => $this->networkData,
  220. ]);
  221. }
  222. }