knowledge-graph-explorer.blade.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <x-filament-panels::page>
  2. <div class="space-y-6">
  3. {{-- 页面标题 --}}
  4. <div class="rounded-xl border border-slate-200 bg-white shadow-sm p-5">
  5. <div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
  6. <div class="space-y-2 text-slate-900">
  7. <h2 class="text-3xl font-bold">知识图谱浏览</h2>
  8. <p class="text-lg text-slate-600">点击任意节点查看详细信息,包括子知识点和技能点</p>
  9. </div>
  10. <div class="flex items-center gap-2">
  11. <a
  12. href="{{ url('admin/knowledge-graph-integration') }}"
  13. class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:text-gray-300 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
  14. >
  15. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  16. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
  17. </svg>
  18. 综合视图
  19. </a>
  20. <a
  21. href="{{ url('admin/knowledge-mindmap') }}"
  22. class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:text-gray-300 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
  23. >
  24. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  25. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
  26. </svg>
  27. 原始脑图
  28. </a>
  29. </div>
  30. </div>
  31. </div>
  32. {{-- 完全复用原版知识图谱代码 --}}
  33. <div
  34. class="rounded-2xl border border-slate-200 shadow-sm bg-white text-slate-900 knowledge-mindmap-card"
  35. x-data="{
  36. graphInstance: null,
  37. stats: { nodes: 0, extraEdges: 0 },
  38. livewireId: '{{ $this->getId() }}',
  39. selectedNode: null,
  40. initEventListener() {
  41. window.addEventListener('mindmap-node-selected', (evt) => {
  42. const model = evt.detail || {};
  43. const code = model?.meta?.code || model?.id;
  44. const mastery = model?.meta?.mastery_level ?? this.graphInstance?.masteryData?.[code]?.mastery_level ?? 0;
  45. const accuracy = model?.meta?.accuracy_rate ?? this.graphInstance?.masteryData?.[code]?.accuracy_rate ?? null;
  46. const attempts = model?.meta?.total_attempts ?? this.graphInstance?.masteryData?.[code]?.total_attempts ?? null;
  47. this.selectedNode = {
  48. id: model?.id,
  49. code,
  50. label: model?.label,
  51. mastery,
  52. accuracy,
  53. attempts,
  54. recommended: model?.meta?.recommended ?? false,
  55. skills: model?.meta?.skills || this.graphInstance?.masteryData?.[code]?.skills || [],
  56. };
  57. // 不再需要通知Livewire,直接在Alpine中处理
  58. // selectedNode 已在上面设置完成
  59. });
  60. },
  61. async initMindmap() {
  62. try {
  63. // 等待G6和自定义组件加载
  64. await this.waitForComponents();
  65. if (!window.G6 || !window.KnowledgeMindmapGraph) {
  66. console.error('G6组件未加载');
  67. return;
  68. }
  69. this.graphInstance = new window.KnowledgeMindmapGraph({
  70. containerId: 'knowledge-graph-viz',
  71. livewireMethod: null, // 不使用自动调用,由Alpine手动处理
  72. livewireId: this.livewireId,
  73. emitSelection: true,
  74. highlightLowMastery: false,
  75. });
  76. await this.graphInstance.init();
  77. this.stats = this.graphInstance.stats;
  78. } catch (error) {
  79. console.error('知识图谱初始化失败:', error);
  80. }
  81. },
  82. async waitForComponents() {
  83. let attempts = 0;
  84. const maxAttempts = 50;
  85. while ((!window.G6 || !window.KnowledgeMindmapGraph) && attempts < maxAttempts) {
  86. await new Promise(resolve => setTimeout(resolve, 100));
  87. attempts++;
  88. }
  89. if (attempts >= maxAttempts) {
  90. throw new Error('G6组件加载超时');
  91. }
  92. }
  93. }"
  94. x-init="initEventListener(); initMindmap()"
  95. data-knowledge-mindmap-root
  96. >
  97. <div class="relative overflow-hidden rounded-2xl border border-slate-200 shadow-sm bg-white text-slate-900">
  98. <div
  99. wire:ignore
  100. id="knowledge-graph-viz"
  101. class="knowledge-mindmap-canvas relative h-[82vh] min-h-[720px] w-full"
  102. >
  103. <div class="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_20%_20%,rgba(255,255,255,0.08),transparent_40%),radial-gradient(circle_at_80%_60%,rgba(255,255,255,0.06),transparent_45%)]"></div>
  104. </div>
  105. </div>
  106. {{-- 选中节点信息 --}}
  107. <template x-if="selectedNode">
  108. <div class="mt-4 grid grid-cols-1 gap-4 lg:grid-cols-3">
  109. <div class="lg:col-span-2 rounded-xl border border-slate-200 bg-white p-4 shadow-sm">
  110. <div class="flex items-start justify-between">
  111. <div>
  112. <p class="text-sm text-slate-500">选中知识点</p>
  113. <h3 class="text-xl font-semibold text-slate-900" x-text="selectedNode.label || selectedNode.code"></h3>
  114. <p class="text-sm text-slate-500 mt-1" x-text="`ID: ${selectedNode.id || ''} · Code: ${selectedNode.code || ''}`"></p>
  115. </div>
  116. <div class="text-right">
  117. <p class="text-sm text-slate-500">掌握度</p>
  118. <p class="text-2xl font-bold" x-text="(selectedNode.mastery * 100).toFixed(1) + '%'"></p>
  119. <p class="text-xs text-slate-500" x-show="selectedNode.accuracy">准确率 <span x-text="(selectedNode.accuracy * 100).toFixed(1) + '%'"></span></p>
  120. <p class="text-xs text-slate-500" x-show="selectedNode.attempts">练习次数 <span x-text="selectedNode.attempts"></span></p>
  121. </div>
  122. </div>
  123. <div class="mt-4">
  124. <p class="text-sm font-semibold text-slate-700 mb-2">技能要点</p>
  125. <div class="flex flex-wrap gap-2">
  126. <template x-if="selectedNode.skills && selectedNode.skills.length">
  127. <template x-for="skill in selectedNode.skills" :key="skill">
  128. <span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-700" x-text="skill"></span>
  129. </template>
  130. </template>
  131. <span x-show="!selectedNode.skills || !selectedNode.skills.length" class="text-sm text-slate-500">暂无技能要点</span>
  132. </div>
  133. <div class="mt-4">
  134. <a
  135. class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-700"
  136. :href="selectedNode.code ? `/admin/knowledge-point-detail?kp_code=${encodeURIComponent(selectedNode.code)}` : '#'"
  137. target="_blank"
  138. >
  139. 查看知识点详情
  140. </a>
  141. </div>
  142. </div>
  143. </div>
  144. <div class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm space-y-2">
  145. <p class="text-sm font-semibold text-slate-700">快速信息</p>
  146. <div class="text-sm text-slate-600 space-y-1">
  147. <p><span class="font-medium text-slate-900">ID:</span><span x-text="selectedNode.id"></span></p>
  148. <p><span class="font-medium text-slate-900">Code:</span><span x-text="selectedNode.code"></span></p>
  149. <p><span class="font-medium text-slate-900">推荐关注:</span><span x-text="selectedNode.recommended ? '是' : '否'"></span></p>
  150. </div>
  151. </div>
  152. </div>
  153. </template>
  154. </div>
  155. {{-- 知识点详情面板 --}}
  156. <template x-if="selectedNode && selectedNode.code !== 'root'">
  157. <div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
  158. {{-- 知识点标题 --}}
  159. <div class="p-6 border-b border-gray-200 dark:border-gray-700">
  160. <div class="flex items-center justify-between">
  161. <div>
  162. <h3 class="text-xl font-bold text-gray-900 dark:text-gray-100" x-text="selectedNode.label || selectedNode.code"></h3>
  163. <div class="mt-1 flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
  164. <span x-text="selectedNode.code"></span>
  165. </div>
  166. </div>
  167. </div>
  168. </div>
  169. {{-- 统计概览 --}}
  170. <div class="p-6 border-b border-gray-200 dark:border-gray-700">
  171. <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
  172. <div class="text-center">
  173. <div class="text-2xl font-bold text-blue-600 dark:text-blue-400">-</div>
  174. <div class="text-sm text-gray-500 dark:text-gray-400 mt-1">总题目数</div>
  175. </div>
  176. <div class="text-center">
  177. <div class="text-2xl font-bold text-green-600 dark:text-green-400">-</div>
  178. <div class="text-sm text-gray-500 dark:text-gray-400 mt-1">直接题目</div>
  179. </div>
  180. <div class="text-center">
  181. <div class="text-2xl font-bold text-yellow-600 dark:text-yellow-400">-</div>
  182. <div class="text-sm text-gray-500 dark:text-gray-400 mt-1">子知识点题目</div>
  183. </div>
  184. <div class="text-center">
  185. <div class="text-2xl font-bold text-purple-600 dark:text-purple-400">-</div>
  186. <div class="text-sm text-gray-500 dark:text-gray-400 mt-1">技能点题目</div>
  187. </div>
  188. </div>
  189. </div>
  190. {{-- 标签页导航 --}}
  191. <div class="px-6 border-b border-gray-200 dark:border-gray-700">
  192. <nav class="flex space-x-8" aria-label="Tabs">
  193. <span class="border-indigo-500 text-indigo-600 dark:text-indigo-400 py-4 px-1 border-b-2 font-medium text-sm">
  194. 知识点详情
  195. </span>
  196. </nav>
  197. </div>
  198. {{-- 标签页内容 --}}
  199. <div class="p-6">
  200. <div class="space-y-4">
  201. <div>
  202. <h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">技能要点</h4>
  203. <div class="flex flex-wrap gap-2">
  204. <template x-if="selectedNode.skills && selectedNode.skills.length">
  205. <template x-for="skill in selectedNode.skills" :key="skill">
  206. <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200" x-text="skill"></span>
  207. </template>
  208. </template>
  209. <span x-show="!selectedNode.skills || !selectedNode.skills.length" class="text-sm text-gray-500 dark:text-gray-400">暂无技能要点</span>
  210. </div>
  211. </div>
  212. </div>
  213. </div>
  214. </div>
  215. </template>
  216. {{-- 统计信息 --}}
  217. <div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
  218. <div class="flex items-center justify-between">
  219. <div class="flex items-center gap-2">
  220. <svg class="w-5 h-5 text-indigo-600 dark:text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  221. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
  222. </svg>
  223. <span class="text-sm font-medium text-gray-700 dark:text-gray-300">使用说明</span>
  224. </div>
  225. <div class="flex gap-4 text-sm text-gray-600 dark:text-gray-400">
  226. <div class="flex items-center gap-1">
  227. <div class="w-3 h-3 rounded-full bg-blue-500"></div>
  228. <span>知识点</span>
  229. </div>
  230. <div class="flex items-center gap-1">
  231. <div class="w-3 h-3 rounded-full bg-green-500"></div>
  232. <span>已掌握</span>
  233. </div>
  234. <div class="flex items-center gap-1">
  235. <div class="w-3 h-3 rounded-full bg-yellow-500"></div>
  236. <span>需加强</span>
  237. </div>
  238. </div>
  239. </div>
  240. </div>
  241. </div>
  242. </x-filament-panels::page>
  243. @push('styles')
  244. <style>
  245. .knowledge-mindmap-canvas {
  246. background: #ffffff;
  247. color: #0f172a;
  248. }
  249. .g6-grid {
  250. opacity: 0.08;
  251. }
  252. </style>
  253. <script src="https://unpkg.com/@antv/g6@4.8.24/dist/g6.min.js"></script>
  254. @endpush
  255. @push('scripts')
  256. {{-- 加载现有的知识图谱可视化类 --}}
  257. <script src="{{ asset('js/g6-custom-node.js') }}"></script>
  258. <script src="{{ asset('js/knowledge-mindmap-graph.js') }}"></script>
  259. <script>
  260. document.addEventListener('livewire:initialized', () => {
  261. // 监听错误事件
  262. Livewire.on('error', (event) => {
  263. console.error('Error:', event.message);
  264. });
  265. // 节点选择事件
  266. Livewire.on('nodeSelected', (event) => {
  267. console.log('Node selected:', event);
  268. });
  269. });
  270. </script>
  271. @endpush