student-dashboard.blade.php 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. <div class="min-h-screen bg-gray-50 p-8">
  2. {{-- 页面标题区域 --}}
  3. <div class="mb-8">
  4. <div class="bg-white rounded-xl shadow-sm p-6 border border-gray-200">
  5. <div class="flex items-center justify-between mb-6">
  6. <div>
  7. <h1 class="text-3xl font-bold text-gray-900 flex items-center">
  8. <svg class="w-8 h-8 mr-3 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  9. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
  10. </svg>
  11. 学生仪表板
  12. </h1>
  13. <p class="mt-2 text-sm text-gray-600 ml-11">
  14. 全面展示学生的学习分析数据,包括掌握度、技能熟练度、提效分析和学习路径
  15. </p>
  16. </div>
  17. </div>
  18. {{-- 选择器区域 --}}
  19. <div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
  20. <div class="flex flex-col gap-3 lg:flex-row lg:items-center">
  21. {{-- 选择老师(老师登录时隐藏) --}}
  22. @if(!$this->isTeacher)
  23. <div class="form-control w-full lg:flex-1">
  24. <label class="label">
  25. <span class="label-text font-medium">选择老师</span>
  26. </label>
  27. <select
  28. wire:model.live="teacherId"
  29. class="select select-bordered w-full h-11"
  30. >
  31. <option value="">请选择老师...</option>
  32. @foreach($this->teachers as $teacher)
  33. <option value="{{ $teacher->teacher_id }}">
  34. {{ trim($teacher->name ?? $teacher->teacher_id) . ($teacher->subject ? " ({$teacher->subject})" : '') }}
  35. </option>
  36. @endforeach
  37. </select>
  38. <label class="label">
  39. <span class="label-text text-xs text-gray-500">选择要查看的老师</span>
  40. </label>
  41. </div>
  42. @endif
  43. {{-- 选择学生 --}}
  44. <div class="form-control w-full lg:flex-1">
  45. <label class="label">
  46. <span class="label-text font-medium">选择学生</span>
  47. </label>
  48. <select
  49. wire:model.live="studentId"
  50. class="select select-bordered w-full h-11"
  51. @if(empty($teacherId)) disabled @endif
  52. >
  53. <option value="">
  54. @if(empty($teacherId))
  55. 请先选择老师
  56. @else
  57. 请选择学生...
  58. @endif
  59. </option>
  60. @foreach($this->students as $student)
  61. <option value="{{ $student->student_id }}">
  62. {{ trim($student->name ?? $student->student_id) . " ({$student->grade} - {$student->class_name})" }}
  63. </option>
  64. @endforeach
  65. </select>
  66. <label class="label">
  67. <span class="label-text text-xs text-gray-500">选择要查看的学生</span>
  68. </label>
  69. </div>
  70. {{-- 刷新按钮 --}}
  71. <div class="w-full lg:w-auto flex items-center lg:pt-6">
  72. <button
  73. wire:click="loadDashboardData"
  74. wire:loading.attr="disabled"
  75. class="inline-flex items-center justify-center w-full h-11 px-6 border border-transparent text-sm font-medium rounded-lg shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200"
  76. >
  77. <svg wire:loading class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
  78. <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
  79. <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
  80. </svg>
  81. 刷新数据
  82. </button>
  83. </div>
  84. </div>
  85. </div>
  86. </div>
  87. </div>
  88. @php
  89. $mindmapAvg = collect($mindmapMasteryData ?? [])
  90. ->pluck('mastery_level')
  91. ->filter(fn ($v) => is_numeric($v))
  92. ->avg() ?? 0;
  93. $mindmapError = collect($mindmapMasteryData ?? [])
  94. ->map(fn ($item) => is_array($item) ? max(0, 1 - ($item['accuracy_rate'] ?? 0)) : 0)
  95. ->avg() ?? 0;
  96. @endphp
  97. <div
  98. class="mb-10 space-y-4"
  99. x-data="studentMindmapPanel('{{ $this->getId() }}')"
  100. x-init="initEventListener(); initMindmap()"
  101. data-knowledge-mindmap-root
  102. >
  103. <div class="relative overflow-hidden rounded-2xl border border-slate-200 shadow-sm bg-white">
  104. <div
  105. wire:ignore
  106. id="student-mindmap"
  107. class="knowledge-mindmap-canvas relative h-[56vh] min-h-[460px] w-full"
  108. >
  109. </div>
  110. </div>
  111. {{-- 内联知识点详情 --}}
  112. <template x-if="selectedNode">
  113. <div class="grid grid-cols-1 gap-4 lg:grid-cols-3">
  114. <div class="lg:col-span-2 rounded-xl border border-slate-200 bg-white p-4 shadow-sm">
  115. <div class="flex items-start justify-between">
  116. <div>
  117. <p class="text-sm text-slate-500">选中知识点</p>
  118. <h3 class="text-xl font-semibold text-slate-900" x-text="selectedNode.label || selectedNode.code"></h3>
  119. <p class="text-sm text-slate-500 mt-1" x-text="`ID: ${selectedNode.id || ''} · Code: ${selectedNode.code || ''}`"></p>
  120. </div>
  121. <div class="text-right">
  122. <p class="text-sm text-slate-500">掌握度</p>
  123. <p class="text-2xl font-bold" x-text="(selectedNode.mastery * 100).toFixed(1) + '%'"></p>
  124. <p class="text-xs text-slate-500" x-show="selectedNode.accuracy">准确率 <span x-text="(selectedNode.accuracy * 100).toFixed(1) + '%'"></span></p>
  125. <p class="text-xs text-slate-500" x-show="selectedNode.attempts">练习次数 <span x-text="selectedNode.attempts"></span></p>
  126. </div>
  127. </div>
  128. <div class="mt-4">
  129. <p class="text-sm font-semibold text-slate-700 mb-2">技能要点</p>
  130. <div class="flex flex-wrap gap-2">
  131. <template x-if="selectedNode.skills && selectedNode.skills.length">
  132. <template x-for="skill in selectedNode.skills" :key="skill">
  133. <span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-700" x-text="skill"></span>
  134. </template>
  135. </template>
  136. <span x-show="!selectedNode.skills || !selectedNode.skills.length" class="text-sm text-slate-500">暂无技能要点</span>
  137. </div>
  138. <div class="mt-4">
  139. <a
  140. class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-700"
  141. :href="selectedNode.code ? `/admin/knowledge-point-detail?kp_code=${encodeURIComponent(selectedNode.code)}` : '#'"
  142. target="_blank"
  143. >
  144. 查看知识点详情
  145. </a>
  146. </div>
  147. </div>
  148. </div>
  149. <div class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm space-y-2">
  150. <p class="text-sm font-semibold text-slate-700">快速信息</p>
  151. <div class="text-sm text-slate-600 space-y-1">
  152. <p><span class="font-medium text-slate-900">ID:</span><span x-text="selectedNode.id"></span></p>
  153. <p><span class="font-medium text-slate-900">Code:</span><span x-text="selectedNode.code"></span></p>
  154. <p><span class="font-medium text-slate-900">推荐关注:</span><span x-text="selectedNode.recommended ? '是' : '否'"></span></p>
  155. </div>
  156. </div>
  157. </div>
  158. </template>
  159. <x-mindmap.detail-drawer
  160. :open="$mindmapDrawerOpen"
  161. :details="$mindmapNodeDetails"
  162. closeAction="closeMindmapDrawer"
  163. selectAction="openMindmapDrawer"
  164. panelTitle="知识点详情"
  165. />
  166. </div>
  167. {{-- 错误提示 --}}
  168. @if ($errorMessage)
  169. <div class="mb-8">
  170. <div class="bg-red-50 border border-red-200 rounded-xl p-4">
  171. <div class="flex items-start">
  172. <div class="flex-shrink-0">
  173. <svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
  174. <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
  175. </svg>
  176. </div>
  177. <div class="ml-3">
  178. <h3 class="text-sm font-medium text-red-800">加载错误</h3>
  179. <div class="mt-2 text-sm text-red-700">
  180. <p>{{ $errorMessage }}</p>
  181. </div>
  182. </div>
  183. </div>
  184. </div>
  185. </div>
  186. @endif
  187. {{-- 加载状态 --}}
  188. @if ($isLoading)
  189. <div class="mb-8">
  190. <div class="bg-white rounded-xl shadow-sm p-12 border border-gray-200">
  191. <div class="flex flex-col items-center justify-center">
  192. <svg class="animate-spin h-12 w-12 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
  193. <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
  194. <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
  195. </svg>
  196. <p class="mt-4 text-sm text-gray-600">正在加载数据,请稍候...</p>
  197. </div>
  198. </div>
  199. </div>
  200. @else
  201. {{-- 加载完成后显示内容 --}}
  202. {{-- 快速概览卡片 --}}
  203. {{-- 快速概览卡片 --}}
  204. <div class="mb-8">
  205. <div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
  206. {{-- 掌握度概览 --}}
  207. @if (isset($dashboardData['mastery']['overview']))
  208. <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
  209. <div class="p-6">
  210. <div class="flex items-center justify-between">
  211. <div class="flex items-center">
  212. <div class="flex-shrink-0">
  213. <div class="w-12 h-12 bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg">
  214. <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  215. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
  216. </svg>
  217. </div>
  218. </div>
  219. <div class="ml-4">
  220. <p class="text-sm font-medium text-gray-600">平均掌握度</p>
  221. <p class="text-2xl font-bold text-gray-900 mt-1">
  222. {{ number_format($dashboardData['mastery']['overview']['average_mastery_level'] * 100, 1) }}%
  223. </p>
  224. </div>
  225. </div>
  226. </div>
  227. </div>
  228. <div class="bg-gray-50 px-6 py-4 border-t border-gray-100">
  229. <div class="flex justify-between text-xs">
  230. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
  231. 已掌握: {{ $dashboardData['mastery']['overview']['mastered_knowledge_points'] }}
  232. </span>
  233. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
  234. 薄弱: {{ $dashboardData['mastery']['overview']['weak_knowledge_points'] }}
  235. </span>
  236. </div>
  237. </div>
  238. </div>
  239. @endif
  240. {{-- 技能熟练度概览 --}}
  241. @if (isset($dashboardData['skill']['summary']) && !empty($dashboardData['skill']['summary']['total_skills']))
  242. <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
  243. <div class="p-6">
  244. <div class="flex items-center justify-between">
  245. <div class="flex items-center">
  246. <div class="flex-shrink-0">
  247. <div class="w-12 h-12 bg-gradient-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center shadow-lg">
  248. <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  249. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
  250. </svg>
  251. </div>
  252. </div>
  253. <div class="ml-4">
  254. <p class="text-sm font-medium text-gray-600">技能熟练度</p>
  255. <p class="text-2xl font-bold text-gray-900 mt-1">
  256. {{ number_format($dashboardData['skill']['summary']['average_proficiency_level'] * 100, 1) }}%
  257. </p>
  258. </div>
  259. </div>
  260. </div>
  261. </div>
  262. <div class="bg-gray-50 px-6 py-4 border-t border-gray-100">
  263. <div class="flex justify-between text-xs">
  264. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
  265. 技能: {{ $dashboardData['skill']['summary']['total_skills'] }}
  266. </span>
  267. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
  268. 练习: {{ $dashboardData['skill']['summary']['total_questions_attempted'] }}
  269. </span>
  270. </div>
  271. </div>
  272. </div>
  273. @endif
  274. {{-- 学习提效概览 --}}
  275. @if (isset($dashboardData['prediction']['quick']))
  276. <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
  277. <div class="p-6">
  278. <div class="flex items-center justify-between">
  279. <div class="flex items-center">
  280. <div class="flex-shrink-0">
  281. <div class="w-12 h-12 bg-gradient-to-br from-yellow-500 to-orange-500 rounded-xl flex items-center justify-center shadow-lg">
  282. <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  283. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
  284. </svg>
  285. </div>
  286. </div>
  287. <div class="ml-4">
  288. <p class="text-sm font-medium text-gray-600">预期提效</p>
  289. <p class="text-2xl font-bold text-gray-900 mt-1">
  290. +{{ $dashboardData['prediction']['quick']['quick_prediction']['improvement_potential'] ?? 0 }}
  291. <span class="text-lg text-gray-500">分</span>
  292. </p>
  293. </div>
  294. </div>
  295. </div>
  296. </div>
  297. <div class="bg-gray-50 px-6 py-4 border-t border-gray-100">
  298. <div class="flex justify-between text-xs">
  299. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800">
  300. 学习: {{ $dashboardData['prediction']['quick']['quick_prediction']['estimated_study_hours'] ?? 0 }}小时
  301. </span>
  302. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
  303. 置信度: {{ number_format(($dashboardData['prediction']['quick']['quick_prediction']['confidence_level'] ?? 0) * 100, 0) }}%
  304. </span>
  305. </div>
  306. </div>
  307. </div>
  308. @endif
  309. {{-- 学习路径 --}}
  310. @if (isset($dashboardData['learning_path']['analytics']))
  311. <div class="bg-white overflow-hidden shadow-sm rounded-xl border border-gray-200 hover:shadow-md transition-shadow duration-200">
  312. <div class="p-6">
  313. <div class="flex items-center justify-between">
  314. <div class="flex items-center">
  315. <div class="flex-shrink-0">
  316. <div class="w-12 h-12 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
  317. <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  318. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"></path>
  319. </svg>
  320. </div>
  321. </div>
  322. <div class="ml-4">
  323. <p class="text-sm font-medium text-gray-600">活跃路径</p>
  324. <p class="text-2xl font-bold text-gray-900 mt-1">
  325. {{ $dashboardData['learning_path']['analytics']['active_paths'] ?? 0 }}
  326. </p>
  327. </div>
  328. </div>
  329. </div>
  330. </div>
  331. <div class="bg-gray-50 px-6 py-4 border-t border-gray-100">
  332. <div class="flex justify-between text-xs">
  333. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
  334. 已完成: {{ $dashboardData['learning_path']['analytics']['completed_paths'] ?? 0 }}
  335. </span>
  336. <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800">
  337. 效率: {{ number_format(($dashboardData['learning_path']['analytics']['average_efficiency_score'] ?? 0) * 100, 0) }}%
  338. </span>
  339. </div>
  340. </div>
  341. </div>
  342. @endif
  343. </div>
  344. </div>
  345. {{-- 主要内容区域:统一流式网格 --}}
  346. <div class="mb-8">
  347. <div class="grid grid-cols-1 gap-6 xl:grid-cols-2 2xl:grid-cols-3 items-start">
  348. {{-- 掌握度分析 --}}
  349. <div class="bg-white shadow-sm rounded-xl border border-gray-200 xl:col-span-2">
  350. <div class="px-6 py-5 border-b border-gray-100 flex items-center justify-between">
  351. <h3 class="text-lg font-semibold text-gray-900 flex items-center">
  352. <svg class="w-5 h-5 mr-2 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  353. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
  354. </svg>
  355. 知识点掌握度
  356. </h3>
  357. </div>
  358. <div class="p-6">
  359. @if (isset($dashboardData['mastery']['overview']['total_knowledge_points']))
  360. <div class="space-y-4">
  361. <div class="flex items-center justify-between text-sm">
  362. <span class="text-gray-600">总知识点数</span>
  363. <span class="font-medium text-gray-900">{{ $dashboardData['mastery']['overview']['total_knowledge_points'] }}</span>
  364. </div>
  365. <div class="w-full bg-gray-200 rounded-full h-2">
  366. <div class="bg-indigo-600 h-2 rounded-full" style="width: {{ $dashboardData['mastery']['overview']['average_mastery_level'] * 100 }}%"></div>
  367. </div>
  368. <div class="grid grid-cols-3 gap-4 mt-4">
  369. <div class="text-center">
  370. <div class="text-2xl font-semibold text-green-600">{{ $dashboardData['mastery']['overview']['mastered_knowledge_points'] }}</div>
  371. <div class="text-xs text-gray-500">已掌握 (≥85%)</div>
  372. </div>
  373. <div class="text-center">
  374. <div class="text-2xl font-semibold text-blue-600">{{ $dashboardData['mastery']['overview']['good_knowledge_points'] }}</div>
  375. <div class="text-xs text-gray-500">良好 (70-85%)</div>
  376. </div>
  377. <div class="text-center">
  378. <div class="text-2xl font-semibold text-red-600">{{ $dashboardData['mastery']['overview']['weak_knowledge_points'] }}</div>
  379. <div class="text-xs text-gray-500">薄弱 (<50%)</div>
  380. </div>
  381. </div>
  382. @if (!empty($dashboardData['mastery']['overview']['weak_knowledge_points_list']))
  383. <div class="mt-6">
  384. <h4 class="text-sm font-medium text-gray-900 mb-3">薄弱知识点</h4>
  385. <div class="space-y-2">
  386. @foreach (array_slice($dashboardData['mastery']['overview']['weak_knowledge_points_list'], 0, 5) as $weak)
  387. <div class="flex items-center justify-between p-3 bg-red-50 rounded-lg">
  388. <div class="flex items-center">
  389. <div class="w-2 h-2 bg-red-500 rounded-full mr-3"></div>
  390. <span class="text-sm font-medium text-gray-900">{{ $weak['kp_code'] ?? $weak['knowledge_point_code'] ?? 'N/A' }}</span>
  391. </div>
  392. <div class="flex items-center space-x-3">
  393. <span class="text-sm text-gray-600">{{ number_format(($weak['mastery_level'] ?? $weak['mastery'] ?? 0) * 100, 1) }}%</span>
  394. <a
  395. href="{{ url('/admin/intelligent-exam-generation') }}?student_id={{ $studentId }}&kp_code={{ urlencode($weak['kp_code'] ?? $weak['knowledge_point_code'] ?? '') }}"
  396. class="text-xs text-indigo-600 hover:text-indigo-800"
  397. target="_blank"
  398. >
  399. 去做题
  400. </a>
  401. </div>
  402. </div>
  403. @endforeach
  404. </div>
  405. </div>
  406. @endif
  407. </div>
  408. @else
  409. <div class="text-center py-8 text-gray-500">
  410. 暂无掌握度数据
  411. </div>
  412. @endif
  413. </div>
  414. </div>
  415. {{-- 技能熟练度 --}}
  416. @php
  417. $skillData = $dashboardData['skill']['proficiency']['data'] ?? [];
  418. if (!is_array($skillData)) {
  419. $skillData = [];
  420. }
  421. @endphp
  422. @if (!empty($skillData))
  423. <div class="bg-white shadow-sm rounded-xl border border-gray-200">
  424. <div class="px-6 py-5 border-b border-gray-100 flex items-center justify-between">
  425. <h3 class="text-lg font-semibold text-gray-900 flex items-center">
  426. <svg class="w-5 h-5 mr-2 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  427. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
  428. </svg>
  429. 技能熟练度
  430. </h3>
  431. <button
  432. wire:click="batchUpdateSkills"
  433. class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-lg text-green-700 bg-green-100 hover:bg-green-200 transition-colors duration-150"
  434. >
  435. <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  436. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
  437. </svg>
  438. 批量更新
  439. </button>
  440. </div>
  441. <div class="p-6">
  442. <div class="space-y-4">
  443. @foreach (array_slice($skillData, 0, 5) as $skill)
  444. <div class="flex items-center justify-between">
  445. <div class="flex items-center flex-1">
  446. <div class="w-2 h-2 bg-green-500 rounded-full mr-3"></div>
  447. <div class="flex-1">
  448. <div class="text-sm font-medium text-gray-900">{{ $skill['skill_name'] ?? 'N/A' }}</div>
  449. <div class="w-full bg-gray-200 rounded-full h-1.5 mt-1">
  450. <div class="bg-green-500 h-1.5 rounded-full" style="width: {{ (($skill['proficiency_level'] ?? 0) * 100) }}%"></div>
  451. </div>
  452. </div>
  453. </div>
  454. <div class="ml-4 text-right">
  455. <div class="text-sm font-semibold text-gray-900">{{ number_format(($skill['proficiency_level'] ?? 0) * 100, 1) }}%</div>
  456. <div class="text-xs text-gray-500">{{ $skill['total_questions_attempted'] ?? 0 }}题</div>
  457. </div>
  458. </div>
  459. @endforeach
  460. </div>
  461. </div>
  462. </div>
  463. @endif
  464. {{-- 学习提效 --}}
  465. <div class="bg-white shadow-sm rounded-xl border border-gray-200">
  466. <div class="px-6 py-5 border-b border-gray-100 flex items-center justify-between">
  467. <h3 class="text-lg font-semibold text-gray-900 flex items-center">
  468. <svg class="w-5 h-5 mr-2 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  469. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
  470. </svg>
  471. 学习提效
  472. </h3>
  473. </div>
  474. <div class="p-6">
  475. {{-- 快速预测结果 --}}
  476. @if (isset($dashboardData['prediction']['quick']) && !empty($dashboardData['prediction']['quick']['quick_prediction']))
  477. @php
  478. $quickPrediction = $dashboardData['prediction']['quick']['quick_prediction'];
  479. $round05 = function($v) {
  480. return number_format(round(((float) $v) * 2) / 2, 1);
  481. };
  482. @endphp
  483. {{-- 主要预测信息卡片 --}}
  484. <div class="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg p-6 mb-6 border border-blue-200">
  485. <div class="flex items-center justify-between mb-4">
  486. <div>
  487. <h4 class="text-lg font-semibold text-gray-900">学习提效分析</h4>
  488. <p class="text-sm text-gray-600 mt-1">基于当前学习数据的分析</p>
  489. </div>
  490. <div class="text-right">
  491. <div class="text-2xl font-bold text-green-600">
  492. +{{ $round05($quickPrediction['improvement_potential'] ?? 0) }}
  493. </div>
  494. <div class="text-sm text-gray-500">预期提效</div>
  495. </div>
  496. </div>
  497. <div class="grid grid-cols-3 gap-4 mb-4">
  498. <div class="text-center">
  499. <div class="text-lg font-semibold text-gray-900">
  500. {{ $round05($quickPrediction['current_score'] ?? 0) }}
  501. </div>
  502. <div class="text-xs text-gray-500">当前分值</div>
  503. </div>
  504. <div class="text-center">
  505. <div class="text-lg font-semibold text-blue-600">
  506. {{ $round05($quickPrediction['predicted_score'] ?? 0) }}
  507. </div>
  508. <div class="text-xs text-gray-500">预测分值</div>
  509. </div>
  510. <div class="text-center">
  511. <div class="text-lg font-semibold text-purple-600">
  512. {{ $quickPrediction['estimated_study_hours'] ?? 0 }}h
  513. </div>
  514. <div class="text-xs text-gray-500">建议学习时间</div>
  515. </div>
  516. </div>
  517. <div class="flex items-center justify-between">
  518. <div class="flex items-center space-x-2">
  519. <span class="text-sm text-gray-600">置信度:</span>
  520. <div class="flex items-center">
  521. <div class="w-20 bg-gray-200 rounded-full h-2 mr-2">
  522. <div class="bg-blue-600 h-2 rounded-full" style="width: {{ ($quickPrediction['confidence_level'] ?? 0) * 100 }}%"></div>
  523. </div>
  524. <span class="text-sm font-medium text-gray-900">{{ number_format(($quickPrediction['confidence_level'] ?? 0) * 100, 0) }}%</span>
  525. </div>
  526. </div>
  527. </div>
  528. </div>
  529. {{-- 优先学习主题 --}}
  530. @if (!empty($quickPrediction['priority_topics']))
  531. <div class="mb-6">
  532. <h5 class="text-sm font-medium text-gray-900 mb-3 flex items-center">
  533. <svg class="w-4 h-4 mr-2 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  534. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
  535. </svg>
  536. 优先学习主题 (前5个)
  537. </h5>
  538. <div class="space-y-2">
  539. @foreach (array_slice($quickPrediction['priority_topics'], 0, 5) as $topic)
  540. <div class="flex items-center justify-between p-3 bg-yellow-50 rounded-lg border border-yellow-200">
  541. <div class="flex items-center space-x-3">
  542. <div class="w-8 h-8 bg-yellow-100 rounded-full flex items-center justify-center">
  543. <span class="text-xs font-medium text-yellow-800">{{ substr($topic['kp_code'] ?? '', -2) }}</span>
  544. </div>
  545. <div>
  546. <div class="text-sm font-medium text-gray-900">{{ $topic['kp_code'] ?? 'N/A' }}</div>
  547. <div class="text-xs text-gray-500">掌握度: {{ number_format(($topic['mastery_level'] ?? 0) * 100, 1) }}%</div>
  548. </div>
  549. </div>
  550. <div class="text-right">
  551. <div class="text-sm font-medium text-gray-900">优先级</div>
  552. <div class="text-xs text-gray-500">{{ number_format($topic['priority_score'] ?? 0, 2) }}</div>
  553. </div>
  554. </div>
  555. @endforeach
  556. </div>
  557. </div>
  558. @endif
  559. {{-- 学习建议 --}}
  560. @if (!empty($quickPrediction['recommended_actions']))
  561. <div>
  562. <h5 class="text-sm font-medium text-gray-900 mb-3 flex items-center">
  563. <svg class="w-4 h-4 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  564. <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>
  565. </svg>
  566. 个性化学习建议
  567. </h5>
  568. <div class="space-y-3">
  569. @foreach (array_slice($quickPrediction['recommended_actions'], 0, 3) as $recommendation)
  570. <div class="p-3 bg-blue-50 rounded-lg border border-blue-200">
  571. <div class="flex items-center justify-between mb-2">
  572. <span class="text-sm font-medium text-gray-900">{{ $recommendation['kp_code'] ?? 'N/A' }}</span>
  573. <span class="text-xs text-gray-500">
  574. {{ number_format(($recommendation['current_mastery'] ?? 0) * 100, 0) }}% → {{ number_format(($recommendation['target_mastery'] ?? 0) * 100, 0) }}%
  575. </span>
  576. </div>
  577. <div class="flex flex-wrap gap-1">
  578. @foreach (($recommendation['actions'] ?? []) as $action)
  579. <span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800">
  580. {{ $action }}
  581. </span>
  582. @endforeach
  583. </div>
  584. </div>
  585. @endforeach
  586. </div>
  587. </div>
  588. @endif
  589. @if (!empty($studentId))
  590. <div class="mt-6">
  591. <a
  592. href="{{ url('/admin/intelligent-exam-generation') }}?student_id={{ $studentId }}"
  593. class="inline-flex items-center px-4 py-2 rounded-lg bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-700"
  594. >
  595. 去做匹配的题目,降低无效的刷题时间,提高效率
  596. </a>
  597. </div>
  598. @endif
  599. {{-- 历史预测记录 --}}
  600. @elseif (!empty($dashboardData['prediction']['list']['predictions']))
  601. @php
  602. $historicalPredictions = $dashboardData['prediction']['list']['predictions'];
  603. @endphp
  604. <div class="space-y-4">
  605. <h5 class="text-sm font-medium text-gray-900 mb-3">历史预测记录</h5>
  606. @foreach (array_slice($historicalPredictions, 0, 3) as $prediction)
  607. <div class="p-4 border border-gray-200 rounded-lg">
  608. <div class="flex items-center justify-between mb-2">
  609. <div class="text-sm font-medium text-gray-900">{{ $prediction['target_entity'] ?? 'N/A' }}</div>
  610. <span class="text-xs text-gray-500">{{ $prediction['prediction_date'] ?? date('m-d') }}</span>
  611. </div>
  612. <div class="flex items-center justify-between">
  613. <div class="text-sm text-gray-600">
  614. 当前: {{ $prediction['current_score'] ?? 0 }} →
  615. <span class="font-semibold text-gray-900">{{ $prediction['predicted_score'] ?? 0 }}</span>
  616. </div>
  617. <div class="text-sm font-semibold text-green-600">
  618. +{{ number_format(($prediction['predicted_score'] ?? 0) - ($prediction['current_score'] ?? 0), 1) }}
  619. </div>
  620. </div>
  621. </div>
  622. @endforeach
  623. </div>
  624. @else
  625. <div class="text-center py-8 text-gray-500">
  626. <div class="mb-4">
  627. <svg class="w-12 h-12 mx-auto text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  628. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
  629. </svg>
  630. </div>
  631. <p class="text-sm font-medium text-gray-900 mb-1">暂无预测数据</p>
  632. <p class="text-xs text-gray-500">等待分析数据刷新后再查看</p>
  633. </div>
  634. @endif
  635. </div>
  636. </div>
  637. <div class="xl:col-span-3">
  638. <x-mistake-book-panel :data="$mistakePanel" :student-id="$studentId" :teacher-id="$teacherId" />
  639. </div>
  640. </div>
  641. </div>
  642. </div>
  643. @endif
  644. @push('styles')
  645. <style>
  646. .knowledge-mindmap-canvas {
  647. background: #ffffff;
  648. }
  649. </style>
  650. @endpush
  651. @push('scripts')
  652. <script src="https://gw.alipayobjects.com/os/lib/antv/g6/4.8.24/dist/g6.min.js"></script>
  653. <script src="{{ asset('js/g6-custom-node.js') }}"></script>
  654. <script src="{{ asset('js/knowledge-mindmap-graph.js') }}"></script>
  655. <script>
  656. document.addEventListener('alpine:init', () => {
  657. window.studentMindmapPanel = (livewireId = null) => ({
  658. graphInstance: null,
  659. stats: { nodes: 0, extraEdges: 0 },
  660. livewireId,
  661. selectedNode: null,
  662. initEventListener() {
  663. window.addEventListener('mindmap-node-selected', (evt) => {
  664. const model = evt.detail || {};
  665. const code = model?.meta?.code || model?.id;
  666. const mastery = model?.meta?.mastery_level ?? this.graphInstance?.masteryData?.[code]?.mastery_level ?? 0;
  667. const accuracy = model?.meta?.accuracy_rate ?? this.graphInstance?.masteryData?.[code]?.accuracy_rate ?? null;
  668. const attempts = model?.meta?.total_attempts ?? this.graphInstance?.masteryData?.[code]?.total_attempts ?? null;
  669. this.selectedNode = {
  670. id: model?.id,
  671. code,
  672. label: model?.label,
  673. mastery,
  674. accuracy,
  675. attempts,
  676. recommended: model?.meta?.recommended ?? false,
  677. skills: model?.meta?.skills || this.graphInstance?.masteryData?.[code]?.skills || [],
  678. };
  679. });
  680. },
  681. async initMindmap() {
  682. if (!window.KnowledgeMindmapGraph) {
  683. return;
  684. }
  685. this.graphInstance = new KnowledgeMindmapGraph({
  686. containerId: 'student-mindmap',
  687. livewireMethod: 'openMindmapDrawer',
  688. highlightLowMastery: true,
  689. livewireId: this.livewireId,
  690. showRelationEdges: false,
  691. onNodeSelect: (model) => {
  692. const code = model?.meta?.code || model?.id;
  693. const mastery = model?.meta?.mastery_level ?? this.graphInstance?.masteryData?.[code]?.mastery_level ?? 0;
  694. const accuracy = model?.meta?.accuracy_rate ?? this.graphInstance?.masteryData?.[code]?.accuracy_rate ?? null;
  695. const attempts = model?.meta?.total_attempts ?? this.graphInstance?.masteryData?.[code]?.total_attempts ?? null;
  696. this.selectedNode = {
  697. id: model?.id,
  698. code,
  699. label: model?.label,
  700. mastery,
  701. accuracy,
  702. attempts,
  703. recommended: model?.meta?.recommended ?? false,
  704. skills: model?.meta?.skills || this.graphInstance?.masteryData?.[code]?.skills || [],
  705. };
  706. },
  707. });
  708. this.graphInstance.masteryData = @js($mindmapMasteryData ?? []);
  709. await this.graphInstance.init();
  710. this.stats = this.graphInstance.stats;
  711. },
  712. });
  713. });
  714. </script>
  715. @endpush
  716. {{-- 通知脚本 --}}
  717. <script>
  718. document.addEventListener('notify', (event) => {
  719. const message = event.detail.message;
  720. const type = event.detail.type || 'info';
  721. alert(message);
  722. });
  723. </script>