312
This commit is contained in:
parent
0c63c20201
commit
593f13ca15
|
|
@ -13,7 +13,9 @@
|
|||
| 7 | 个人特质分析 | `/report/personality-traits` | 网页截图 | CategoryType=2 结果+结论 | ⬜ 骨架 |
|
||||
| 8 | 40项细分能力分析 | `/report/sub-abilities?recordId=3` | 网页截图 | CategoryType=3 雷达图(40项分数) | ✅ 已完成 |
|
||||
| 8.1 | 细分能力排序TOP10 | `/report/sub-abilities-ranking?recordId=3` | 网页截图 | CategoryType=3 最强/偏弱TOP10柱状图+排名 | ✅ 已完成 |
|
||||
| 9 | 先天学习类型分析 | `/report/learning-types` | 网页截图 | CategoryType=4 结果+结论 | ⬜ 骨架 |
|
||||
| 8.2 | 细分能力精准分析(×8) | `/report/sub-ability-detail?recordId=3&parentId=1` | 网页截图 | 单智能5项子能力雷达图+柱状图+结论 | ✅ 已完成 |
|
||||
| 8.3 | 细分能力精准分析-竖向柱状图(×8) | `/report/sub-ability-detail-v?recordId=3&parentId=8` | 网页截图 | 单智能5项子能力雷达图+竖向柱状图+结论 | ✅ 已完成 |
|
||||
| 9 | 先天学习类型分析 | `/report/learning-types?recordId=3` | 网页截图 | CategoryType=4 雷达图+竖向柱状图(多色)+结论 | ✅ 已完成 |
|
||||
| 10 | 学习关键能力分析 | `/report/learning-abilities` | 网页截图 | CategoryType=5 结果+结论 | ⬜ 骨架 |
|
||||
| 11 | 科学大脑类型分析 | `/report/brain-types` | 网页截图 | CategoryType=6 结果+结论 | ⬜ 骨架 |
|
||||
| 12 | 性格类型分析 | `/report/character-types` | 网页截图 | CategoryType=7 结果+结论 | ⬜ 骨架 |
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
@page "/report/learning-types"
|
||||
@model MiAssessment.Api.Pages.Report.LearningTypesModel
|
||||
@{
|
||||
ViewData["Title"] = "先天学习类型分析";
|
||||
ViewData["PageTitle"] = "先天学习类型分析";
|
||||
ViewData["Title"] = "先天学习类型";
|
||||
ViewData["PageTitle"] = null;
|
||||
ViewData["PageNumber"] = null;
|
||||
}
|
||||
|
||||
@if (!Model.IsSuccess)
|
||||
|
|
@ -13,7 +14,242 @@
|
|||
}
|
||||
else
|
||||
{
|
||||
<div class="learning-types-content">
|
||||
<p class="no-content">先天学习类型分析占位 - 具体内容后续实现</p>
|
||||
<div class="lt-page">
|
||||
<!-- 板块标题 -->
|
||||
<div class="lt-section-title">4、先天学习类型</div>
|
||||
|
||||
<!-- 上半部分:雷达图 + 竖向柱状图 -->
|
||||
<div class="lt-charts">
|
||||
<!-- 左侧:雷达图 -->
|
||||
<div class="lt-chart-panel">
|
||||
<div class="lt-chart-title">先天学习类型</div>
|
||||
<canvas id="radarChart" width="480" height="380"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:竖向柱状图 -->
|
||||
<div class="lt-chart-panel">
|
||||
<div class="lt-chart-title">先天学习类型排名</div>
|
||||
<canvas id="barChart" width="540" height="380"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下半部分:结论卡片 -->
|
||||
<div class="lt-conclusions">
|
||||
<!-- 最强能力解读 -->
|
||||
<div class="lt-conclusion-card lt-card-strong">
|
||||
<div class="lt-badge lt-badge-strong">最强能力解读</div>
|
||||
<div class="lt-conclusion-content">
|
||||
@if (Model.StrongestConclusion != null)
|
||||
{
|
||||
<text>【@Model.StrongestName】</text>
|
||||
@Html.Raw(Model.StrongestConclusion.Content?.Replace("\n", "<br/>") ?? "暂无结论")
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="no-content">暂无结论数据</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 较弱能力解读 -->
|
||||
<div class="lt-conclusion-card lt-card-weak">
|
||||
<div class="lt-badge lt-badge-weak">较弱能力解读</div>
|
||||
<div class="lt-conclusion-content">
|
||||
@if (Model.WeakestConclusion != null)
|
||||
{
|
||||
@Html.Raw(Model.WeakestConclusion.Content?.Replace("\n", "<br/>") ?? "暂无结论")
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="no-content">暂无结论数据</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link rel="stylesheet" href="/css/pages/learning-types.css" />
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
window.__deferRenderComplete = true;
|
||||
|
||||
// 雷达图数据(按 CategoryId 排序)
|
||||
var radarLabels = [@Html.Raw(string.Join(",", Model.Items.Select(x => $"\"{x.CategoryName.Replace("学习类型", "").Replace("型", "") + "型"}\"")))];
|
||||
var radarScores = [@Html.Raw(string.Join(",", Model.Items.Select(x => x.Score.ToString("F0"))))];
|
||||
var maxScore = Math.ceil(Math.max(...radarScores) / 2) * 2 + 2;
|
||||
|
||||
// 柱状图数据(按分数降序)
|
||||
var barLabels = [@Html.Raw(string.Join(",", Model.ItemsByScore.Select(x => $"\"{x.CategoryName.Replace("学习类型", "").Replace("型", "") + "型"}\"")))];
|
||||
var barScores = [@Html.Raw(string.Join(",", Model.ItemsByScore.Select(x => x.Score.ToString("F0"))))];
|
||||
var avgScore = @Model.AverageScore.ToString("F1");
|
||||
var barMax = Math.ceil(Math.max(...barScores) / 2) * 2 + 2;
|
||||
|
||||
// 每根柱子不同颜色
|
||||
var barColors = ['#4A90E2', '#52A06A', '#F5A623', '#E8913A', '#2ABFBF'];
|
||||
var barColorsAlpha = ['rgba(74,144,226,0.7)', 'rgba(82,160,106,0.7)', 'rgba(245,166,35,0.7)', 'rgba(232,145,58,0.7)', 'rgba(42,191,191,0.7)'];
|
||||
|
||||
// 雷达图主色(红/粉色系,与设计图一致)
|
||||
var radarColor = '#E88B9C';
|
||||
var radarColorAlpha = 'rgba(232, 139, 156, 0.6)';
|
||||
|
||||
// ---- 雷达图 ----
|
||||
var ctxRadar = document.getElementById('radarChart').getContext('2d');
|
||||
new Chart(ctxRadar, {
|
||||
type: 'radar',
|
||||
data: {
|
||||
labels: radarLabels,
|
||||
datasets: [{
|
||||
data: radarScores,
|
||||
backgroundColor: 'rgba(232, 139, 156, 0.12)',
|
||||
borderColor: 'rgba(232, 139, 156, 0.8)',
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: 'rgba(232, 139, 156, 0.9)',
|
||||
pointBorderColor: '#fff',
|
||||
pointRadius: 4,
|
||||
pointBorderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
r: {
|
||||
beginAtZero: true,
|
||||
max: maxScore,
|
||||
ticks: {
|
||||
stepSize: 2,
|
||||
font: { size: 11 },
|
||||
backdropColor: 'transparent',
|
||||
color: '#999'
|
||||
},
|
||||
pointLabels: {
|
||||
font: { size: 13, weight: '500' },
|
||||
color: '#333',
|
||||
padding: 10
|
||||
},
|
||||
grid: { color: 'rgba(0,0,0,0.06)' },
|
||||
angleLines: { color: 'rgba(0,0,0,0.06)' }
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [{
|
||||
afterDatasetsDraw: function(chart) {
|
||||
var meta = chart.getDatasetMeta(0);
|
||||
var ctx2 = chart.ctx;
|
||||
ctx2.font = 'bold 12px sans-serif';
|
||||
ctx2.fillStyle = radarColor;
|
||||
ctx2.textBaseline = 'middle';
|
||||
meta.data.forEach(function(point, i) {
|
||||
var val = radarScores[i];
|
||||
var cx = chart.scales.r.xCenter;
|
||||
var cy = chart.scales.r.yCenter;
|
||||
var dx = point.x - cx;
|
||||
var dy = point.y - cy;
|
||||
var dist = Math.sqrt(dx * dx + dy * dy);
|
||||
if (dist === 0) dist = 1;
|
||||
var ox = (dx / dist) * 16;
|
||||
var oy = (dy / dist) * 16;
|
||||
ctx2.textAlign = 'center';
|
||||
ctx2.fillText(val, point.x + ox, point.y + oy);
|
||||
});
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
// ---- 竖向柱状图 + 平均值横线 ----
|
||||
var avgLinePlugin = {
|
||||
id: 'avgLine',
|
||||
afterDatasetsDraw: function(chart) {
|
||||
var xScale = chart.scales.x;
|
||||
var yScale = chart.scales.y;
|
||||
var ctx = chart.ctx;
|
||||
var yPos = yScale.getPixelForValue(avgScore);
|
||||
|
||||
ctx.save();
|
||||
ctx.strokeStyle = '#E8913A';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.setLineDash([6, 4]);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(xScale.left, yPos);
|
||||
ctx.lineTo(xScale.right, yPos);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
ctx.save();
|
||||
ctx.fillStyle = '#E8913A';
|
||||
ctx.font = 'bold 12px "Microsoft YaHei", sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('平均值:' + avgScore, xScale.left + 4, yPos - 4);
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
var barDataLabelPlugin = {
|
||||
id: 'barDataLabels',
|
||||
afterDatasetsDraw: function(chart) {
|
||||
var ctx = chart.ctx;
|
||||
var meta = chart.getDatasetMeta(0);
|
||||
var dataset = chart.data.datasets[0];
|
||||
ctx.font = 'bold 12px "Microsoft YaHei", sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
meta.data.forEach(function(bar, i) {
|
||||
var val = dataset.data[i];
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.fillText(val, bar.x, bar.y - 4);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var ctxBar = document.getElementById('barChart').getContext('2d');
|
||||
new Chart(ctxBar, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: barLabels,
|
||||
datasets: [{
|
||||
data: barScores,
|
||||
backgroundColor: barColorsAlpha,
|
||||
borderColor: barColors,
|
||||
borderWidth: 0,
|
||||
borderRadius: 3,
|
||||
barThickness: 52
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: barMax,
|
||||
ticks: {
|
||||
stepSize: 2,
|
||||
font: { size: 11 },
|
||||
color: '#999'
|
||||
},
|
||||
grid: { color: 'rgba(0,0,0,0.05)' }
|
||||
},
|
||||
x: {
|
||||
ticks: {
|
||||
font: { size: 12 },
|
||||
color: '#666'
|
||||
},
|
||||
grid: { display: false }
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
padding: { top: 25 }
|
||||
}
|
||||
},
|
||||
plugins: [avgLinePlugin, barDataLabelPlugin]
|
||||
});
|
||||
|
||||
document.body.setAttribute('data-render-complete', 'true');
|
||||
</script>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,125 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using MiAssessment.Core.Interfaces;
|
||||
using MiAssessment.Model.Data;
|
||||
using MiAssessment.Model.Models.Report;
|
||||
|
||||
namespace MiAssessment.Api.Pages.Report;
|
||||
|
||||
/// <summary>
|
||||
/// 先天学习类型分析 PageModel
|
||||
/// 先天学习类型分析页 PageModel
|
||||
/// 路由:/report/learning-types?recordId=3
|
||||
/// CategoryType=4 的5种学习类型:视觉型、思考型、全脑型、体察型、听觉型
|
||||
/// </summary>
|
||||
public class LearningTypesModel : ReportPageModelBase
|
||||
{
|
||||
public LearningTypesModel(IReportDataService reportDataService)
|
||||
/// <summary>
|
||||
/// 学习类型列表(按 CategoryId 排序,雷达图用)
|
||||
/// </summary>
|
||||
public List<CategoryResultDataDto> Items { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 按分数降序排列(柱状图用)
|
||||
/// </summary>
|
||||
public List<CategoryResultDataDto> ItemsByScore { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 平均分
|
||||
/// </summary>
|
||||
public decimal AverageScore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最强类型名称
|
||||
/// </summary>
|
||||
public string StrongestName { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 最强类型结论
|
||||
/// </summary>
|
||||
public ConclusionDataDto? StrongestConclusion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 较弱类型名称
|
||||
/// </summary>
|
||||
public string WeakestName { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 较弱类型结论
|
||||
/// </summary>
|
||||
public ConclusionDataDto? WeakestConclusion { get; set; }
|
||||
|
||||
private readonly MiAssessmentDbContext _dbContext;
|
||||
|
||||
public LearningTypesModel(IReportDataService reportDataService, MiAssessmentDbContext dbContext)
|
||||
: base(reportDataService)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
protected override async Task OnDataLoadedAsync()
|
||||
{
|
||||
if (ReportData?.ResultsByType == null ||
|
||||
!ReportData.ResultsByType.TryGetValue(4, out var allItems))
|
||||
{
|
||||
ErrorMessage = "缺少先天学习类型数据";
|
||||
return;
|
||||
}
|
||||
|
||||
Items = allItems.OrderBy(x => x.CategoryId).ToList();
|
||||
|
||||
if (Items.Count == 0)
|
||||
{
|
||||
ErrorMessage = "未找到先天学习类型数据";
|
||||
return;
|
||||
}
|
||||
|
||||
ItemsByScore = Items.OrderByDescending(x => x.Score).ToList();
|
||||
AverageScore = Math.Round(Items.Average(x => x.Score), 1);
|
||||
|
||||
// 最强类型结论
|
||||
var strongest = ItemsByScore.First();
|
||||
StrongestName = strongest.CategoryName;
|
||||
if (ReportData.ConclusionsByCategory.TryGetValue(strongest.CategoryId, out var sc))
|
||||
{
|
||||
StrongestConclusion = sc;
|
||||
}
|
||||
else
|
||||
{
|
||||
StrongestConclusion = await GetTemplateConclusionAsync(strongest.CategoryId, 1);
|
||||
}
|
||||
|
||||
// 较弱类型结论
|
||||
var weakest = ItemsByScore.Last();
|
||||
WeakestName = weakest.CategoryName;
|
||||
if (ReportData.ConclusionsByCategory.TryGetValue(weakest.CategoryId, out var wc))
|
||||
{
|
||||
WeakestConclusion = wc;
|
||||
}
|
||||
else
|
||||
{
|
||||
WeakestConclusion = await GetTemplateConclusionAsync(weakest.CategoryId, 4);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 report_conclusions 模板表查询结论(fallback)
|
||||
/// </summary>
|
||||
private async Task<ConclusionDataDto?> GetTemplateConclusionAsync(long categoryId, int conclusionType)
|
||||
{
|
||||
var template = await _dbContext.ReportConclusions
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t =>
|
||||
t.CategoryId == categoryId &&
|
||||
t.ConclusionType == conclusionType &&
|
||||
!t.IsDeleted);
|
||||
|
||||
if (template == null) return null;
|
||||
|
||||
return new ConclusionDataDto
|
||||
{
|
||||
CategoryId = template.CategoryId,
|
||||
ConclusionType = template.ConclusionType,
|
||||
Title = template.Title,
|
||||
Content = template.Content
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,253 @@
|
|||
@page "/report/sub-ability-detail"
|
||||
@model MiAssessment.Api.Pages.Report.SubAbilityDetailModel
|
||||
@{
|
||||
ViewData["Title"] = "细分能力精准分析";
|
||||
ViewData["PageTitle"] = null;
|
||||
ViewData["PageNumber"] = null;
|
||||
}
|
||||
|
||||
@if (!Model.IsSuccess)
|
||||
{
|
||||
<div class="report-error" data-render-error="true">
|
||||
<p>@Model.ErrorMessage</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sad-page">
|
||||
<!-- 板块标题 -->
|
||||
<div class="sad-section-title">3.@(Model.SectionIndex)、个人40项细分能力精准分析</div>
|
||||
|
||||
<!-- 上半部分:雷达图 + 柱状图 -->
|
||||
<div class="sad-charts">
|
||||
<!-- 左侧:雷达图 -->
|
||||
<div class="sad-chart-panel">
|
||||
<div class="sad-chart-title" style="color: @Model.ChartColor">@(Model.ParentName)能力分析</div>
|
||||
<canvas id="radarChart" width="480" height="380"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:横向柱状图 -->
|
||||
<div class="sad-chart-panel">
|
||||
<div class="sad-chart-title" style="color: @Model.ChartColor">@(Model.ParentName)能力分析排序</div>
|
||||
<canvas id="barChart" width="540" height="380"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下半部分:结论卡片 -->
|
||||
<div class="sad-conclusions">
|
||||
<!-- 最强能力解读 -->
|
||||
<div class="sad-conclusion-card sad-card-strong">
|
||||
<div class="sad-badge sad-badge-strong">最强能力解读</div>
|
||||
<div class="sad-ability-name">@Model.StrongestName</div>
|
||||
<div class="sad-conclusion-content">
|
||||
@if (Model.StrongestConclusion != null)
|
||||
{
|
||||
@Html.Raw(Model.StrongestConclusion.Content?.Replace("\n", "<br/>") ?? "暂无结论")
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="no-content">暂无结论数据</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 较弱能力解读 -->
|
||||
<div class="sad-conclusion-card sad-card-weak">
|
||||
<div class="sad-badge sad-badge-weak">较弱能力解读</div>
|
||||
<div class="sad-ability-name">@Model.WeakestName</div>
|
||||
<div class="sad-conclusion-content">
|
||||
@if (Model.WeakestConclusion != null)
|
||||
{
|
||||
@Html.Raw(Model.WeakestConclusion.Content?.Replace("\n", "<br/>") ?? "暂无结论")
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="no-content">暂无结论数据</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link rel="stylesheet" href="/css/pages/sub-ability-detail.css" />
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
window.__deferRenderComplete = true;
|
||||
|
||||
// 雷达图数据(按 CategoryId 排序)
|
||||
var radarLabels = [@Html.Raw(string.Join(",", Model.SubItems.Select(x => $"\"{x.CategoryName}\"")))];
|
||||
var radarScores = [@Html.Raw(string.Join(",", Model.SubItems.Select(x => x.Score.ToString("F0"))))];
|
||||
var maxScore = 20;
|
||||
|
||||
// 柱状图数据(按分数降序)
|
||||
var barLabels = [@Html.Raw(string.Join(",", Model.SubItemsByScore.Select(x => $"\"{x.CategoryName}\"")))];
|
||||
var barScores = [@Html.Raw(string.Join(",", Model.SubItemsByScore.Select(x => x.Score.ToString("F0"))))];
|
||||
var avgScore = @Model.AverageScore.ToString("F1");
|
||||
var barMax = Math.ceil(Math.max(...barScores) / 5) * 5 + 5;
|
||||
|
||||
// 配色
|
||||
var chartColor = '@Model.ChartColor';
|
||||
var chartColorAlpha = '@Model.ChartColorAlpha';
|
||||
|
||||
// ---- 雷达图 ----
|
||||
var ctxRadar = document.getElementById('radarChart').getContext('2d');
|
||||
new Chart(ctxRadar, {
|
||||
type: 'radar',
|
||||
data: {
|
||||
labels: radarLabels,
|
||||
datasets: [{
|
||||
data: radarScores,
|
||||
backgroundColor: chartColorAlpha.replace('0.6)', '0.12)'),
|
||||
borderColor: chartColorAlpha.replace('0.6)', '0.8)'),
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: chartColorAlpha.replace('0.6)', '0.9)'),
|
||||
pointBorderColor: '#fff',
|
||||
pointRadius: 4,
|
||||
pointBorderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
r: {
|
||||
beginAtZero: true,
|
||||
max: maxScore,
|
||||
ticks: {
|
||||
stepSize: 5,
|
||||
font: { size: 11 },
|
||||
backdropColor: 'transparent',
|
||||
color: '#999'
|
||||
},
|
||||
pointLabels: {
|
||||
font: { size: 13, weight: '500' },
|
||||
color: '#333',
|
||||
padding: 10
|
||||
},
|
||||
grid: { color: 'rgba(0,0,0,0.06)' },
|
||||
angleLines: { color: 'rgba(0,0,0,0.06)' }
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [{
|
||||
afterDatasetsDraw: function(chart) {
|
||||
var meta = chart.getDatasetMeta(0);
|
||||
var ctx2 = chart.ctx;
|
||||
ctx2.font = 'bold 12px sans-serif';
|
||||
ctx2.fillStyle = chartColor;
|
||||
ctx2.textBaseline = 'middle';
|
||||
meta.data.forEach(function(point, i) {
|
||||
var val = radarScores[i];
|
||||
var cx = chart.scales.r.xCenter;
|
||||
var cy = chart.scales.r.yCenter;
|
||||
var dx = point.x - cx;
|
||||
var dy = point.y - cy;
|
||||
var dist = Math.sqrt(dx * dx + dy * dy);
|
||||
if (dist === 0) dist = 1;
|
||||
var ox = (dx / dist) * 16;
|
||||
var oy = (dy / dist) * 16;
|
||||
ctx2.textAlign = 'center';
|
||||
ctx2.fillText(val, point.x + ox, point.y + oy);
|
||||
});
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
// ---- 横向柱状图 + 平均值线 ----
|
||||
var avgLinePlugin = {
|
||||
id: 'avgLine',
|
||||
afterDatasetsDraw: function(chart) {
|
||||
var xScale = chart.scales.x;
|
||||
var yScale = chart.scales.y;
|
||||
var ctx = chart.ctx;
|
||||
var xPos = xScale.getPixelForValue(avgScore);
|
||||
|
||||
// 画竖线
|
||||
ctx.save();
|
||||
ctx.strokeStyle = '#52A06A';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.setLineDash([6, 4]);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(xPos, yScale.top);
|
||||
ctx.lineTo(xPos, yScale.bottom);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
// 画标签
|
||||
ctx.save();
|
||||
ctx.fillStyle = '#52A06A';
|
||||
ctx.font = 'bold 12px "Microsoft YaHei", sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('平均值:' + avgScore, xPos, yScale.top - 8);
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
var barDataLabelPlugin = {
|
||||
id: 'barDataLabels',
|
||||
afterDatasetsDraw: function(chart) {
|
||||
var ctx = chart.ctx;
|
||||
var meta = chart.getDatasetMeta(0);
|
||||
var dataset = chart.data.datasets[0];
|
||||
ctx.font = 'bold 12px "Microsoft YaHei", sans-serif';
|
||||
ctx.textBaseline = 'middle';
|
||||
meta.data.forEach(function(bar, i) {
|
||||
var val = dataset.data[i];
|
||||
ctx.fillStyle = chartColor;
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(val, bar.x + 4, bar.y);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var ctxBar = document.getElementById('barChart').getContext('2d');
|
||||
new Chart(ctxBar, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: barLabels,
|
||||
datasets: [{
|
||||
data: barScores,
|
||||
backgroundColor: chartColorAlpha,
|
||||
borderColor: chartColor,
|
||||
borderWidth: 0,
|
||||
borderRadius: 2,
|
||||
barThickness: 32
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
max: barMax,
|
||||
ticks: {
|
||||
font: { size: 11 },
|
||||
color: '#999'
|
||||
},
|
||||
grid: { color: 'rgba(0,0,0,0.05)' }
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
font: { size: 13 },
|
||||
color: '#666'
|
||||
},
|
||||
grid: { display: false }
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
padding: { right: 30, top: 20 }
|
||||
}
|
||||
},
|
||||
plugins: [avgLinePlugin, barDataLabelPlugin]
|
||||
});
|
||||
|
||||
document.body.setAttribute('data-render-complete', 'true');
|
||||
</script>
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using MiAssessment.Core.Interfaces;
|
||||
using MiAssessment.Model.Data;
|
||||
using MiAssessment.Model.Models.Report;
|
||||
|
||||
namespace MiAssessment.Api.Pages.Report;
|
||||
|
||||
/// <summary>
|
||||
/// 40项细分能力精准分析页 PageModel
|
||||
/// 按父分类(八大智能)展示该智能下5项子能力的雷达图、柱状图和结论
|
||||
/// 路由:/report/sub-ability-detail?recordId=3&parentId=1
|
||||
/// </summary>
|
||||
public class SubAbilityDetailModel : ReportPageModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 父分类ID(八大智能之一),从 query string 绑定
|
||||
/// </summary>
|
||||
[Microsoft.AspNetCore.Mvc.BindProperty(SupportsGet = true, Name = "parentId")]
|
||||
public long ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 父分类名称(如"语言智能")
|
||||
/// </summary>
|
||||
public string ParentName { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 该智能下的子能力列表(按 CategoryId 排序)
|
||||
/// </summary>
|
||||
public List<CategoryResultDataDto> SubItems { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 按分数降序排列(用于柱状图)
|
||||
/// </summary>
|
||||
public List<CategoryResultDataDto> SubItemsByScore { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 平均分
|
||||
/// </summary>
|
||||
public decimal AverageScore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最强能力结论(ConclusionType=1 或 2,取分数最高的)
|
||||
/// </summary>
|
||||
public ConclusionDataDto? StrongestConclusion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最强能力名称
|
||||
/// </summary>
|
||||
public string StrongestName { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 较弱能力结论(ConclusionType=3 或 4,取分数最低的)
|
||||
/// </summary>
|
||||
public ConclusionDataDto? WeakestConclusion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 较弱能力名称
|
||||
/// </summary>
|
||||
public string WeakestName { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 序号(1-8,用于标题显示)
|
||||
/// </summary>
|
||||
public int SectionIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 统计图主色(根据 parentId 不同)
|
||||
/// </summary>
|
||||
public string ChartColor { get; set; } = "#4A90E2";
|
||||
|
||||
/// <summary>
|
||||
/// 统计图主色透明版
|
||||
/// </summary>
|
||||
public string ChartColorAlpha { get; set; } = "rgba(74, 144, 226, 0.6)";
|
||||
|
||||
private readonly MiAssessmentDbContext _dbContext;
|
||||
|
||||
public SubAbilityDetailModel(IReportDataService reportDataService, MiAssessmentDbContext dbContext)
|
||||
: base(reportDataService)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
protected override async Task OnDataLoadedAsync()
|
||||
{
|
||||
if (ParentId <= 0)
|
||||
{
|
||||
ErrorMessage = "缺少父分类参数";
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReportData?.ResultsByType == null ||
|
||||
!ReportData.ResultsByType.TryGetValue(3, out var allItems))
|
||||
{
|
||||
ErrorMessage = "缺少细分能力数据";
|
||||
return;
|
||||
}
|
||||
|
||||
// 筛选该父分类下的子能力
|
||||
SubItems = allItems
|
||||
.Where(x => x.ParentId == ParentId)
|
||||
.OrderBy(x => x.CategoryId)
|
||||
.ToList();
|
||||
|
||||
if (SubItems.Count == 0)
|
||||
{
|
||||
ErrorMessage = $"未找到父分类 {ParentId} 的子能力数据";
|
||||
return;
|
||||
}
|
||||
|
||||
// 按分数降序(柱状图用)
|
||||
SubItemsByScore = SubItems.OrderByDescending(x => x.Score).ToList();
|
||||
|
||||
// 平均分
|
||||
AverageScore = Math.Round(SubItems.Average(x => x.Score), 1);
|
||||
|
||||
// 父分类名称 — 从 CategoryType=1 的结果中查找
|
||||
if (ReportData.ResultsByType.TryGetValue(1, out var type1Items))
|
||||
{
|
||||
var parent = type1Items.FirstOrDefault(x => x.CategoryId == ParentId);
|
||||
if (parent != null)
|
||||
{
|
||||
ParentName = parent.CategoryName;
|
||||
}
|
||||
}
|
||||
|
||||
// 序号
|
||||
SectionIndex = (int)ParentId;
|
||||
|
||||
// 按智能类型设置统计图配色
|
||||
(ChartColor, ChartColorAlpha) = ParentId switch
|
||||
{
|
||||
1 => ("#4A90E2", "rgba(74, 144, 226, 0.6)"), // 语言 - 蓝色
|
||||
2 => ("#2ABFBF", "rgba(42, 191, 191, 0.6)"), // 逻辑数学 - 青色
|
||||
3 => ("#87CEEB", "rgba(135, 206, 235, 0.6)"), // 音乐 - 天蓝色
|
||||
4 => ("#E88B9C", "rgba(232, 139, 156, 0.6)"), // 自然探索 - 粉色
|
||||
5 => ("#52A06A", "rgba(82, 160, 106, 0.6)"), // 人际交往(社交) - 绿色
|
||||
6 => ("#F5A623", "rgba(245, 166, 35, 0.6)"), // 自我察觉 - 黄色
|
||||
7 => ("#E8913A", "rgba(232, 145, 58, 0.6)"), // 视觉空间 - 橙色
|
||||
8 => ("#4A90E2", "rgba(74, 144, 226, 0.6)"), // 肢体运动 - 蓝色
|
||||
_ => ("#4A90E2", "rgba(74, 144, 226, 0.6)")
|
||||
};
|
||||
|
||||
// 最强能力结论(分数最高的子能力)
|
||||
var strongest = SubItemsByScore.First();
|
||||
StrongestName = strongest.CategoryName;
|
||||
if (ReportData.ConclusionsByCategory.TryGetValue(strongest.CategoryId, out var sc))
|
||||
{
|
||||
StrongestConclusion = sc;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从模板表查 ConclusionType=1(最强)的结论
|
||||
StrongestConclusion = await GetTemplateConclusionAsync(strongest.CategoryId, 1);
|
||||
}
|
||||
|
||||
// 较弱能力结论(分数最低的子能力)
|
||||
var weakest = SubItemsByScore.Last();
|
||||
WeakestName = weakest.CategoryName;
|
||||
if (ReportData.ConclusionsByCategory.TryGetValue(weakest.CategoryId, out var wc))
|
||||
{
|
||||
WeakestConclusion = wc;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从模板表查 ConclusionType=4(最弱)的结论
|
||||
WeakestConclusion = await GetTemplateConclusionAsync(weakest.CategoryId, 4);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 report_conclusions 模板表查询结论(fallback)
|
||||
/// </summary>
|
||||
private async Task<ConclusionDataDto?> GetTemplateConclusionAsync(long categoryId, int conclusionType)
|
||||
{
|
||||
var template = await _dbContext.ReportConclusions
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t =>
|
||||
t.CategoryId == categoryId &&
|
||||
t.ConclusionType == conclusionType &&
|
||||
!t.IsDeleted);
|
||||
|
||||
if (template == null) return null;
|
||||
|
||||
return new ConclusionDataDto
|
||||
{
|
||||
CategoryId = template.CategoryId,
|
||||
ConclusionType = template.ConclusionType,
|
||||
Title = template.Title,
|
||||
Content = template.Content
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
@page "/report/sub-ability-detail-v"
|
||||
@model MiAssessment.Api.Pages.Report.SubAbilityDetailVModel
|
||||
@{
|
||||
ViewData["Title"] = "细分能力精准分析";
|
||||
ViewData["PageTitle"] = null;
|
||||
ViewData["PageNumber"] = null;
|
||||
}
|
||||
|
||||
@if (!Model.IsSuccess)
|
||||
{
|
||||
<div class="report-error" data-render-error="true">
|
||||
<p>@Model.ErrorMessage</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sad-page">
|
||||
<!-- 板块标题 -->
|
||||
<div class="sad-section-title">3.@(Model.SectionIndex)、个人40项细分能力精准分析</div>
|
||||
|
||||
<!-- 上半部分:雷达图 + 柱状图 -->
|
||||
<div class="sad-charts">
|
||||
<!-- 左侧:雷达图 -->
|
||||
<div class="sad-chart-panel">
|
||||
<div class="sad-chart-title" style="color: @Model.ChartColor">@(Model.ParentName)能力分析</div>
|
||||
<canvas id="radarChart" width="480" height="380"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:竖向柱状图 -->
|
||||
<div class="sad-chart-panel">
|
||||
<div class="sad-chart-title" style="color: @Model.ChartColor">@(Model.ParentName)能力分析排序</div>
|
||||
<canvas id="barChart" width="540" height="380"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下半部分:结论卡片 -->
|
||||
<div class="sad-conclusions">
|
||||
<!-- 最强能力解读 -->
|
||||
<div class="sad-conclusion-card sad-card-strong">
|
||||
<div class="sad-badge sad-badge-strong">最强能力解读</div>
|
||||
<div class="sad-ability-name">@Model.StrongestName</div>
|
||||
<div class="sad-conclusion-content">
|
||||
@if (Model.StrongestConclusion != null)
|
||||
{
|
||||
@Html.Raw(Model.StrongestConclusion.Content?.Replace("\n", "<br/>") ?? "暂无结论")
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="no-content">暂无结论数据</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 较弱能力解读 -->
|
||||
<div class="sad-conclusion-card sad-card-weak">
|
||||
<div class="sad-badge sad-badge-weak">较弱能力解读</div>
|
||||
<div class="sad-ability-name">@Model.WeakestName</div>
|
||||
<div class="sad-conclusion-content">
|
||||
@if (Model.WeakestConclusion != null)
|
||||
{
|
||||
@Html.Raw(Model.WeakestConclusion.Content?.Replace("\n", "<br/>") ?? "暂无结论")
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="no-content">暂无结论数据</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link rel="stylesheet" href="/css/pages/sub-ability-detail.css" />
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
window.__deferRenderComplete = true;
|
||||
|
||||
// 雷达图数据(按 CategoryId 排序)
|
||||
var radarLabels = [@Html.Raw(string.Join(",", Model.SubItems.Select(x => $"\"{x.CategoryName}\"")))];
|
||||
var radarScores = [@Html.Raw(string.Join(",", Model.SubItems.Select(x => x.Score.ToString("F0"))))];
|
||||
var maxScore = 20;
|
||||
|
||||
// 柱状图数据(按分数降序)
|
||||
var barLabels = [@Html.Raw(string.Join(",", Model.SubItemsByScore.Select(x => $"\"{x.CategoryName}\"")))];
|
||||
var barScores = [@Html.Raw(string.Join(",", Model.SubItemsByScore.Select(x => x.Score.ToString("F0"))))];
|
||||
var avgScore = @Model.AverageScore.ToString("F1");
|
||||
var barMax = Math.ceil(Math.max(...barScores) / 5) * 5 + 5;
|
||||
|
||||
// 配色
|
||||
var chartColor = '@Model.ChartColor';
|
||||
var chartColorAlpha = '@Model.ChartColorAlpha';
|
||||
|
||||
// ---- 雷达图 ----
|
||||
var ctxRadar = document.getElementById('radarChart').getContext('2d');
|
||||
new Chart(ctxRadar, {
|
||||
type: 'radar',
|
||||
data: {
|
||||
labels: radarLabels,
|
||||
datasets: [{
|
||||
data: radarScores,
|
||||
backgroundColor: chartColorAlpha.replace('0.6)', '0.12)'),
|
||||
borderColor: chartColorAlpha.replace('0.6)', '0.8)'),
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: chartColorAlpha.replace('0.6)', '0.9)'),
|
||||
pointBorderColor: '#fff',
|
||||
pointRadius: 4,
|
||||
pointBorderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
r: {
|
||||
beginAtZero: true,
|
||||
max: maxScore,
|
||||
ticks: {
|
||||
stepSize: 5,
|
||||
font: { size: 11 },
|
||||
backdropColor: 'transparent',
|
||||
color: '#999'
|
||||
},
|
||||
pointLabels: {
|
||||
font: { size: 13, weight: '500' },
|
||||
color: '#333',
|
||||
padding: 10
|
||||
},
|
||||
grid: { color: 'rgba(0,0,0,0.06)' },
|
||||
angleLines: { color: 'rgba(0,0,0,0.06)' }
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [{
|
||||
afterDatasetsDraw: function(chart) {
|
||||
var meta = chart.getDatasetMeta(0);
|
||||
var ctx2 = chart.ctx;
|
||||
ctx2.font = 'bold 12px sans-serif';
|
||||
ctx2.fillStyle = chartColor;
|
||||
ctx2.textBaseline = 'middle';
|
||||
meta.data.forEach(function(point, i) {
|
||||
var val = radarScores[i];
|
||||
var cx = chart.scales.r.xCenter;
|
||||
var cy = chart.scales.r.yCenter;
|
||||
var dx = point.x - cx;
|
||||
var dy = point.y - cy;
|
||||
var dist = Math.sqrt(dx * dx + dy * dy);
|
||||
if (dist === 0) dist = 1;
|
||||
var ox = (dx / dist) * 16;
|
||||
var oy = (dy / dist) * 16;
|
||||
ctx2.textAlign = 'center';
|
||||
ctx2.fillText(val, point.x + ox, point.y + oy);
|
||||
});
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
// ---- 竖向柱状图 + 平均值横线 ----
|
||||
var avgLinePlugin = {
|
||||
id: 'avgLine',
|
||||
afterDatasetsDraw: function(chart) {
|
||||
var xScale = chart.scales.x;
|
||||
var yScale = chart.scales.y;
|
||||
var ctx = chart.ctx;
|
||||
var yPos = yScale.getPixelForValue(avgScore);
|
||||
|
||||
// 画横线
|
||||
ctx.save();
|
||||
ctx.strokeStyle = '#E8913A';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.setLineDash([6, 4]);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(xScale.left, yPos);
|
||||
ctx.lineTo(xScale.right, yPos);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
// 画标签(在线左侧)
|
||||
ctx.save();
|
||||
ctx.fillStyle = '#E8913A';
|
||||
ctx.font = 'bold 12px "Microsoft YaHei", sans-serif';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillText('平均值:' + avgScore, xScale.left + 4, yPos - 4);
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
var barDataLabelPlugin = {
|
||||
id: 'barDataLabels',
|
||||
afterDatasetsDraw: function(chart) {
|
||||
var ctx = chart.ctx;
|
||||
var meta = chart.getDatasetMeta(0);
|
||||
var dataset = chart.data.datasets[0];
|
||||
ctx.font = 'bold 12px "Microsoft YaHei", sans-serif';
|
||||
ctx.fillStyle = chartColor;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
meta.data.forEach(function(bar, i) {
|
||||
var val = dataset.data[i];
|
||||
ctx.fillText(val, bar.x, bar.y - 4);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var ctxBar = document.getElementById('barChart').getContext('2d');
|
||||
new Chart(ctxBar, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: barLabels,
|
||||
datasets: [{
|
||||
data: barScores,
|
||||
backgroundColor: chartColorAlpha,
|
||||
borderColor: chartColor,
|
||||
borderWidth: 0,
|
||||
borderRadius: 2,
|
||||
barThickness: 48
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: barMax,
|
||||
ticks: {
|
||||
stepSize: 5,
|
||||
font: { size: 11 },
|
||||
color: '#999'
|
||||
},
|
||||
grid: { color: 'rgba(0,0,0,0.05)' }
|
||||
},
|
||||
x: {
|
||||
ticks: {
|
||||
font: { size: 12 },
|
||||
color: '#666'
|
||||
},
|
||||
grid: { display: false }
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
padding: { top: 25 }
|
||||
}
|
||||
},
|
||||
plugins: [avgLinePlugin, barDataLabelPlugin]
|
||||
});
|
||||
|
||||
document.body.setAttribute('data-render-complete', 'true');
|
||||
</script>
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using MiAssessment.Core.Interfaces;
|
||||
using MiAssessment.Model.Data;
|
||||
|
||||
namespace MiAssessment.Api.Pages.Report;
|
||||
|
||||
/// <summary>
|
||||
/// 40项细分能力精准分析页(竖向柱状图版本)
|
||||
/// 路由:/report/sub-ability-detail-v?recordId=3&parentId=8
|
||||
/// </summary>
|
||||
public class SubAbilityDetailVModel : SubAbilityDetailModel
|
||||
{
|
||||
public SubAbilityDetailVModel(IReportDataService reportDataService, MiAssessmentDbContext dbContext)
|
||||
: base(reportDataService, dbContext)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/* ============================================
|
||||
先天学习类型分析页
|
||||
页面固定尺寸:1309×926px
|
||||
report-page padding: 40px 50px → 可用 1209×846px
|
||||
============================================ */
|
||||
|
||||
.lt-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 板块标题 */
|
||||
.lt-section-title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #E67E73;
|
||||
}
|
||||
|
||||
/* ---- 上半部分:雷达图 + 柱状图并排 ---- */
|
||||
.lt-charts {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.lt-chart-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 图表小标题 */
|
||||
.lt-chart-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #E67E73;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---- 下半部分:结论卡片并排 ---- */
|
||||
.lt-conclusions {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.lt-conclusion-card {
|
||||
flex: 1;
|
||||
border: 3px solid;
|
||||
border-radius: 12px;
|
||||
padding: 34px 24px 18px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.lt-card-strong {
|
||||
border-color: #E67E73;
|
||||
background: #FFF5F5;
|
||||
}
|
||||
|
||||
.lt-card-weak {
|
||||
border-color: #999999;
|
||||
background: #F8F8F8;
|
||||
}
|
||||
|
||||
/* 结论标签 badge */
|
||||
.lt-badge {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-1px);
|
||||
padding: 5px 28px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.lt-badge-strong {
|
||||
background: #C0392B;
|
||||
}
|
||||
|
||||
.lt-badge-weak {
|
||||
background: #999999;
|
||||
}
|
||||
|
||||
/* 结论内容 */
|
||||
.lt-conclusion-content {
|
||||
font-size: 13px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 8;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/* ============================================
|
||||
40项细分能力精准分析 - 单智能详情页
|
||||
页面固定尺寸:1309×926px
|
||||
report-page padding: 40px 50px → 可用 1209×846px
|
||||
============================================ */
|
||||
|
||||
.sad-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 板块标题 */
|
||||
.sad-section-title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* ---- 上半部分:雷达图 + 柱状图并排 ---- */
|
||||
.sad-charts {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.sad-chart-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 图表小标题(颜色由 inline style 控制) */
|
||||
.sad-chart-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---- 下半部分:结论卡片并排 ---- */
|
||||
.sad-conclusions {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sad-conclusion-card {
|
||||
flex: 1;
|
||||
border: 3px solid;
|
||||
border-radius: 12px;
|
||||
padding: 34px 24px 18px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sad-card-strong {
|
||||
border-color: #E67E73;
|
||||
background: #FFF5F5;
|
||||
}
|
||||
|
||||
.sad-card-weak {
|
||||
border-color: #999999;
|
||||
background: #F8F8F8;
|
||||
}
|
||||
|
||||
/* 结论标签 badge(实底背景) */
|
||||
.sad-badge {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-1px);
|
||||
padding: 5px 28px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.sad-badge-strong {
|
||||
background: #C0392B;
|
||||
}
|
||||
|
||||
.sad-badge-weak {
|
||||
background: #999999;
|
||||
}
|
||||
|
||||
/* 结论能力名称 */
|
||||
.sad-ability-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 结论内容 */
|
||||
.sad-conclusion-content {
|
||||
font-size: 13px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 8;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user