21
This commit is contained in:
parent
3fc5433e0c
commit
0c63c20201
|
|
@ -12,6 +12,7 @@
|
|||
| 6 | 较弱智能详情② | `/report/weaker-intelligence?recordId=3&rank=2` | 网页截图 | 绿色模板 rank=2/倒数第2(结论文本) | ✅ 已完成 |
|
||||
| 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 结果+结论 | ⬜ 骨架 |
|
||||
| 10 | 学习关键能力分析 | `/report/learning-abilities` | 网页截图 | CategoryType=5 结果+结论 | ⬜ 骨架 |
|
||||
| 11 | 科学大脑类型分析 | `/report/brain-types` | 网页截图 | CategoryType=6 结果+结论 | ⬜ 骨架 |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
@page "/report/sub-abilities-ranking"
|
||||
@model MiAssessment.Api.Pages.Report.SubAbilitiesRankingModel
|
||||
@{
|
||||
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="sar-page">
|
||||
<!-- 上半部分:两个横向柱状图 -->
|
||||
<div class="sar-charts">
|
||||
<!-- 左侧:最强排序 TOP10 -->
|
||||
<div class="sar-chart-panel">
|
||||
<div class="sar-chart-title sar-title-strong">细分能力最强排序--TOP10</div>
|
||||
<canvas id="strongChart" width="540" height="500"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:偏弱排序 TOP10 -->
|
||||
<div class="sar-chart-panel">
|
||||
<div class="sar-chart-title sar-title-weak">细分能力偏弱排序--TOP10</div>
|
||||
<canvas id="weakChart" width="540" height="500"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下半部分:排名文字列表 -->
|
||||
<div class="sar-rankings">
|
||||
<!-- 最强细分能力排名 -->
|
||||
<div class="sar-rank-card sar-rank-strong">
|
||||
<div class="sar-rank-badge sar-badge-strong">最强细分能力排名</div>
|
||||
<div class="sar-rank-list">
|
||||
@for (var i = 0; i < Model.TopStrongest.Count; i++)
|
||||
{
|
||||
<span class="sar-rank-item">@Model.TopStrongest[i].CategoryName</span>
|
||||
@if (i < Model.TopStrongest.Count - 1)
|
||||
{
|
||||
<span class="sar-rank-sep">、</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="sar-rank-sep">……</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 较弱细分能力排名 -->
|
||||
<div class="sar-rank-card sar-rank-weak">
|
||||
<div class="sar-rank-badge sar-badge-weak">较弱细分能力排名</div>
|
||||
<div class="sar-rank-list">
|
||||
@for (var i = 0; i < Model.TopWeakest.Count; i++)
|
||||
{
|
||||
<span class="sar-rank-item">@Model.TopWeakest[i].CategoryName</span>
|
||||
@if (i < Model.TopWeakest.Count - 1)
|
||||
{
|
||||
<span class="sar-rank-sep">、</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="sar-rank-sep">……</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link rel="stylesheet" href="/css/pages/sub-abilities-ranking.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;
|
||||
|
||||
// 最强 TOP10 数据
|
||||
var strongLabels = [@Html.Raw(string.Join(",", Model.TopStrongest.Select(x => $"\"{x.CategoryName}\"")))];
|
||||
var strongScores = [@Html.Raw(string.Join(",", Model.TopStrongest.Select(x => x.Score.ToString("F0"))))];
|
||||
|
||||
// 偏弱 TOP10 数据
|
||||
var weakLabels = [@Html.Raw(string.Join(",", Model.TopWeakest.Select(x => $"\"{x.CategoryName}\"")))];
|
||||
var weakScores = [@Html.Raw(string.Join(",", Model.TopWeakest.Select(x => x.Score.ToString("F0"))))];
|
||||
|
||||
// 最强分数的最大值(用于坐标轴)
|
||||
var strongMax = Math.ceil(Math.max(...strongScores) / 5) * 5 + 5;
|
||||
var weakMax = Math.ceil(Math.max(...weakScores) / 2) * 2 + 2;
|
||||
|
||||
// 数据点标签插件
|
||||
var dataLabelPlugin = {
|
||||
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 = dataset.borderColor;
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(val, bar.x + 4, bar.y);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 公共配置
|
||||
function createBarConfig(labels, scores, maxVal, barColor, borderColor) {
|
||||
return {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
data: scores,
|
||||
backgroundColor: barColor,
|
||||
borderColor: borderColor,
|
||||
borderWidth: 0,
|
||||
borderRadius: 2,
|
||||
barThickness: 24
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
max: maxVal,
|
||||
ticks: {
|
||||
font: { size: 11 },
|
||||
color: '#999'
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(0,0,0,0.05)'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
font: { size: 12 },
|
||||
color: '#666'
|
||||
},
|
||||
grid: { display: false }
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
padding: { right: 30 }
|
||||
}
|
||||
},
|
||||
plugins: [dataLabelPlugin]
|
||||
};
|
||||
}
|
||||
|
||||
// 渲染最强 TOP10 柱状图(蓝色)
|
||||
var ctx1 = document.getElementById('strongChart').getContext('2d');
|
||||
new Chart(ctx1, createBarConfig(
|
||||
strongLabels, strongScores, strongMax,
|
||||
'rgba(74, 144, 226, 0.6)', '#4A90E2'
|
||||
));
|
||||
|
||||
// 渲染偏弱 TOP10 柱状图(红/粉色)
|
||||
var ctx2 = document.getElementById('weakChart').getContext('2d');
|
||||
new Chart(ctx2, createBarConfig(
|
||||
weakLabels, weakScores, weakMax,
|
||||
'rgba(230, 126, 115, 0.6)', '#E67E73'
|
||||
));
|
||||
|
||||
document.body.setAttribute('data-render-complete', 'true');
|
||||
</script>
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
using MiAssessment.Core.Interfaces;
|
||||
using MiAssessment.Model.Models.Report;
|
||||
|
||||
namespace MiAssessment.Api.Pages.Report;
|
||||
|
||||
/// <summary>
|
||||
/// 40项细分能力排序页 PageModel(最强TOP10 + 偏弱TOP10)
|
||||
/// </summary>
|
||||
public class SubAbilitiesRankingModel : ReportPageModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 最强细分能力 TOP10(按分数降序)
|
||||
/// </summary>
|
||||
public List<CategoryResultDataDto> TopStrongest { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 偏弱细分能力 TOP10(按分数升序)
|
||||
/// </summary>
|
||||
public List<CategoryResultDataDto> TopWeakest { get; set; } = new();
|
||||
|
||||
public SubAbilitiesRankingModel(IReportDataService reportDataService)
|
||||
: base(reportDataService)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task OnDataLoadedAsync()
|
||||
{
|
||||
if (ReportData?.ResultsByType != null &&
|
||||
ReportData.ResultsByType.TryGetValue(3, out var items))
|
||||
{
|
||||
// 最强:按分数降序取前10
|
||||
TopStrongest = items
|
||||
.OrderByDescending(x => x.Score)
|
||||
.ThenBy(x => x.CategoryId)
|
||||
.Take(10)
|
||||
.ToList();
|
||||
|
||||
// 偏弱:按分数升序取前10
|
||||
TopWeakest = items
|
||||
.OrderBy(x => x.Score)
|
||||
.ThenBy(x => x.CategoryId)
|
||||
.Take(10)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
/* ============================================
|
||||
细分能力排序页样式(最强/偏弱 TOP10)
|
||||
页面固定尺寸:1309×926px
|
||||
report-page padding: 40px 50px → 可用 846px
|
||||
============================================ */
|
||||
|
||||
.sar-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: flex-start;
|
||||
gap: 50px;
|
||||
}
|
||||
|
||||
/* ---- 上半部分:两个柱状图并排 ---- */
|
||||
.sar-charts {
|
||||
display: flex;
|
||||
gap: 50px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sar-chart-panel {
|
||||
flex: 1;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
/* 图表标题 */
|
||||
.sar-chart-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.sar-title-strong {
|
||||
color: #4A90E2;
|
||||
}
|
||||
|
||||
.sar-title-weak {
|
||||
color: #E67E73;
|
||||
}
|
||||
|
||||
/* ---- 下半部分:排名文字卡片 ---- */
|
||||
.sar-rankings {
|
||||
display: flex;
|
||||
gap: 50px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sar-rank-card {
|
||||
flex: 1;
|
||||
max-width: 560px;
|
||||
border: 2px solid;
|
||||
border-radius: 12px;
|
||||
padding: 28px 32px 22px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sar-rank-strong {
|
||||
border-color: #E67E73;
|
||||
}
|
||||
|
||||
.sar-rank-weak {
|
||||
border-color: #52A06A;
|
||||
}
|
||||
|
||||
/* 排名标签 badge */
|
||||
.sar-rank-badge {
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 3px 24px;
|
||||
border: 2px solid;
|
||||
border-radius: 6px;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
background: #fff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sar-badge-strong {
|
||||
color: #E67E73;
|
||||
border-color: #E67E73;
|
||||
}
|
||||
|
||||
.sar-badge-weak {
|
||||
color: #52A06A;
|
||||
border-color: #52A06A;
|
||||
}
|
||||
|
||||
/* 排名列表文字 */
|
||||
.sar-rank-list {
|
||||
font-size: 18px;
|
||||
color: var(--text-color);
|
||||
line-height: 2.4;
|
||||
text-align: center;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.sar-rank-item {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.sar-rank-sep {
|
||||
display: inline;
|
||||
margin: 0 2px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user