| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- <div>
- {{-- 加载状态 --}}
- @if ($isLoading)
- <div class="flex items-center justify-center h-96">
- <svg class="animate-spin h-8 w-8 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
- <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>
- </svg>
- <span class="ml-3 text-gray-600">正在加载雷达图...</span>
- </div>
- @elseif ($errorMessage)
- <div class="rounded-md bg-red-50 p-4">
- <div class="flex">
- <div class="flex-shrink-0">
- <svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
- <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" />
- </svg>
- </div>
- <div class="ml-3">
- <h3 class="text-sm font-medium text-red-800">加载失败</h3>
- <div class="mt-2 text-sm text-red-700">
- <p>{{ $errorMessage }}</p>
- </div>
- </div>
- </div>
- </div>
- @elseif (empty($radarData['data']))
- <div class="text-center py-12">
- <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <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>
- </svg>
- <p class="mt-2 text-sm text-gray-500">暂无技能数据</p>
- </div>
- @else
- <div class="space-y-6">
- {{-- 雷达图 --}}
- <div class="relative">
- <canvas id="skillRadarChart" class="w-full" style="max-height: 400px;"></canvas>
- </div>
- {{-- 技能详细列表 --}}
- <div class="bg-gray-50 rounded-lg p-4">
- <h4 class="text-sm font-medium text-gray-900 mb-3">技能详情</h4>
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
- @foreach ($radarData['data'] as $skill)
- <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-200">
- <div class="flex items-center justify-between mb-2">
- <div class="flex items-center">
- <div class="w-3 h-3 rounded-full mr-2" style="background-color: {{ $this->getSkillLevelColor($skill['skill_level']) }}"></div>
- <span class="text-sm font-medium text-gray-900">{{ $skill['skill_name'] }}</span>
- </div>
- <span class="text-xs text-gray-500">{{ $this->getSkillLevelName($skill['skill_level']) }}</span>
- </div>
- <div class="mt-2">
- <div class="flex justify-between text-xs text-gray-600 mb-1">
- <span>熟练度</span>
- <span class="font-medium">{{ number_format($skill['proficiency_level'] * 100, 1) }}%</span>
- </div>
- <div class="w-full bg-gray-200 rounded-full h-1.5">
- <div class="h-1.5 rounded-full" style="width: {{ $skill['proficiency_level'] * 100 }}%; background-color: {{ $this->getSkillLevelColor($skill['skill_level']) }}"></div>
- </div>
- </div>
- <div class="mt-3 grid grid-cols-3 gap-2 text-xs">
- <div>
- <div class="text-gray-500">简单</div>
- <div class="font-medium text-gray-900">{{ number_format(($skill['simple_accuracy'] ?? 0) * 100, 0) }}%</div>
- </div>
- <div>
- <div class="text-gray-500">中等</div>
- <div class="font-medium text-gray-900">{{ number_format(($skill['intermediate_accuracy'] ?? 0) * 100, 0) }}%</div>
- </div>
- <div>
- <div class="text-gray-500">困难</div>
- <div class="font-medium text-gray-900">{{ number_format(($skill['advanced_accuracy'] ?? 0) * 100, 0) }}%</div>
- </div>
- </div>
- <div class="mt-2 text-xs text-gray-500">
- 已练习 {{ $skill['total_questions_attempted'] }} 题
- @if ($skill['practice_streak'] > 0)
- • 连续 {{ $skill['practice_streak'] }} 天
- @endif
- </div>
- </div>
- @endforeach
- </div>
- </div>
- </div>
- {{-- Chart.js 雷达图脚本 --}}
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
- <script>
- document.addEventListener('DOMContentLoaded', function() {
- const ctx = document.getElementById('skillRadarChart');
- if (!ctx) return;
- const skills = @json($radarData['data']);
- const data = {
- labels: skills.map(s => s.skill_name),
- datasets: [{
- label: '技能熟练度',
- data: skills.map(s => s.proficiency_level * 100),
- backgroundColor: 'rgba(59, 130, 246, 0.2)',
- borderColor: 'rgba(59, 130, 246, 1)',
- borderWidth: 2,
- pointBackgroundColor: skills.map(s => {
- const colors = {
- 'beginner': '#ef4444',
- 'elementary': '#f97316',
- 'intermediate': '#eab308',
- 'advanced': '#22c55e',
- 'proficient': '#3b82f6'
- };
- return colors[s.skill_level] || '#9ca3af';
- }),
- pointBorderColor: '#fff',
- pointHoverBackgroundColor: '#fff',
- pointHoverBorderColor: 'rgba(59, 130, 246, 1)',
- pointRadius: 5,
- pointHoverRadius: 7,
- }]
- };
- const config = {
- type: 'radar',
- data: data,
- options: {
- responsive: true,
- maintainAspectRatio: true,
- plugins: {
- legend: {
- display: true,
- position: 'top',
- },
- tooltip: {
- callbacks: {
- label: function(context) {
- const skill = skills[context.dataIndex];
- return [
- `熟练度: ${context.parsed.r.toFixed(1)}%`,
- `等级: ${skill.skill_level}`,
- `已练习: ${skill.total_questions_attempted} 题`
- ];
- }
- }
- }
- },
- scales: {
- r: {
- angleLines: {
- display: true,
- color: 'rgba(0, 0, 0, 0.1)'
- },
- suggestedMin: 0,
- suggestedMax: 100,
- ticks: {
- stepSize: 20,
- callback: function(value) {
- return value + '%';
- }
- },
- grid: {
- color: 'rgba(0, 0, 0, 0.1)'
- },
- pointLabels: {
- font: {
- size: 12
- }
- }
- }
- }
- }
- };
- new Chart(ctx, config);
- });
- </script>
- @endif
- </div>
|