|
|
@@ -86,18 +86,18 @@ $wowLine = static function (int $cur, int $prev): string {
|
|
|
return sprintf('%s%d(%s%.2f%%)%s', $sign, $delta, $sign, $pct, $dir);
|
|
|
};
|
|
|
|
|
|
-/** 总量表环比列:仅增长标绿 */
|
|
|
-$wowLineHtml = static function (int $cur, int $prev) use ($wowLine): string {
|
|
|
+/** 总量表环比列:正增长着色(学案蓝 / 学情橙 / 其余绿) */
|
|
|
+$wowLineHtml = static function (int $cur, int $prev, string $posColor = '#16a34a') use ($wowLine): string {
|
|
|
$plain = $wowLine($cur, $prev);
|
|
|
if ($cur > $prev) {
|
|
|
- return '<span style="color:#16a34a;font-weight:600;">'.$plain.'</span>';
|
|
|
+ return '<span style="color:'.$posColor.';font-weight:600;">'.$plain.'</span>';
|
|
|
}
|
|
|
|
|
|
return $plain;
|
|
|
};
|
|
|
|
|
|
-/** 明细环比列:仅增长标绿 */
|
|
|
-$compareCellHtml = static function (int $cur, int $prev): string {
|
|
|
+/** 环比列:正增长用 $posColor(学案蓝 / 学情橙 / 默认绿) */
|
|
|
+$compareCellHtml = static function (int $cur, int $prev, string $posColor = '#16a34a'): string {
|
|
|
$d = $cur - $prev;
|
|
|
if ($d === 0) {
|
|
|
return '0';
|
|
|
@@ -107,28 +107,33 @@ $compareCellHtml = static function (int $cur, int $prev): string {
|
|
|
return '0';
|
|
|
}
|
|
|
|
|
|
- return '<span style="color:#16a34a;font-weight:600;">+'.$d.'(上0)</span>';
|
|
|
+ return '<span style="color:'.$posColor.';font-weight:600;">+'.$d.'(上0)</span>';
|
|
|
}
|
|
|
$pct = round(($d / $prev) * 100, 1);
|
|
|
$sign = $d > 0 ? '+' : '';
|
|
|
$text = sprintf('%s%d(%s%.1f%%)', $sign, $d, $sign, $pct);
|
|
|
if ($d > 0) {
|
|
|
- return '<span style="color:#16a34a;font-weight:600;">'.$text.'</span>';
|
|
|
+ return '<span style="color:'.$posColor.';font-weight:600;">'.$text.'</span>';
|
|
|
}
|
|
|
|
|
|
return $text;
|
|
|
};
|
|
|
|
|
|
-/** 「本 / 上」并列:仅本侧数字可绿加粗;「 / 」与上周期数字强制黑色(避免 PDF 引擎继承样式) */
|
|
|
-$slashPairGreenHtml = static function (int $cur, int $prev): string {
|
|
|
+/** 「本 / 上」并列:本>上时本侧用强调色(与上方左/右图折线一致);「 / 」与上周期数字固定深灰黑 */
|
|
|
+$slashPairAccentHtml = static function (int $cur, int $prev, string $accent): string {
|
|
|
$rest = '<span style="color:#111827;font-weight:normal;"> / '.$prev.'</span>';
|
|
|
if ($cur > $prev) {
|
|
|
- return '<span style="color:#16a34a;font-weight:700;">'.$cur.'</span>'.$rest;
|
|
|
+ return '<span style="color:'.$accent.';font-weight:700;">'.$cur.'</span>'.$rest;
|
|
|
}
|
|
|
|
|
|
return '<span style="color:#111827;font-weight:normal;">'.$cur.'</span>'.$rest;
|
|
|
};
|
|
|
|
|
|
+/** 与每日对比图一致:学案蓝、学情橙、学生绿 */
|
|
|
+$colorPapers = '#2563eb';
|
|
|
+$colorAnalysis = '#ea580c';
|
|
|
+$colorStudents = '#16a34a';
|
|
|
+
|
|
|
$byTeacher = \Illuminate\Support\Facades\DB::table('papers')
|
|
|
->whereNotNull('teacher_id')
|
|
|
->where('teacher_id', '!=', '')
|
|
|
@@ -290,149 +295,162 @@ $dailySlices = static function (\Carbon\Carbon $from, \Carbon\Carbon $toExclusiv
|
|
|
$curDaily = $dailySlices($startCurrent, $endCurrent);
|
|
|
$prevDaily = $dailySlices($startPrev, $endPrev);
|
|
|
|
|
|
-$buildChartSvg = static function (array $curDaily, array $prevDaily): string {
|
|
|
+/** 左侧:学案(组卷)套数;右侧:学情分析套数(卷去重)。实线本周期,虚线上周期。 */
|
|
|
+$buildDualChartsHtml = static function (array $curDaily, array $prevDaily): string {
|
|
|
$labels = $curDaily['labels'];
|
|
|
$pc = $curDaily['papers'];
|
|
|
$ac = $curDaily['analysis'];
|
|
|
$pp = $prevDaily['papers'];
|
|
|
$ap = $prevDaily['analysis'];
|
|
|
- $maxY = max(1, ...$pc, ...$ac, ...$pp, ...$ap);
|
|
|
- $W = 480;
|
|
|
- $H = 200;
|
|
|
- $padL = 44;
|
|
|
- $padR = 12;
|
|
|
- $padT = 16;
|
|
|
- $padB = 36;
|
|
|
- $gw = $W - $padL - $padR;
|
|
|
- $gh = $H - $padT - $padB;
|
|
|
- $n = 7;
|
|
|
- $xAt = static function (int $i) use ($padL, $gw, $n): float {
|
|
|
- return $padL + ($n <= 1 ? $gw / 2 : $gw * $i / ($n - 1));
|
|
|
- };
|
|
|
- $yAt = static function (int $v) use ($padT, $gh, $maxY): float {
|
|
|
- return $padT + $gh - ($v / $maxY) * $gh;
|
|
|
- };
|
|
|
- $lineWithDots = static function (array $vals, string $stroke, string $dash, float $sw = 1.6) use ($xAt, $yAt, $n): string {
|
|
|
- $pts = [];
|
|
|
- $circles = '';
|
|
|
+
|
|
|
+ $halfSvg = static function (
|
|
|
+ array $labels,
|
|
|
+ array $cur,
|
|
|
+ array $prev,
|
|
|
+ string $strokeCur,
|
|
|
+ string $strokePrev,
|
|
|
+ string $legCur,
|
|
|
+ string $legPrev
|
|
|
+ ): string {
|
|
|
+ $maxY = max(1, ...$cur, ...$prev);
|
|
|
+ $W = 248;
|
|
|
+ $H = 200;
|
|
|
+ $padL = 38;
|
|
|
+ $padR = 8;
|
|
|
+ $padT = 14;
|
|
|
+ $padB = 34;
|
|
|
+ $gw = $W - $padL - $padR;
|
|
|
+ $gh = $H - $padT - $padB;
|
|
|
+ $n = 7;
|
|
|
+ $xAt = static function (int $i) use ($padL, $gw, $n): float {
|
|
|
+ return $padL + ($n <= 1 ? $gw / 2 : $gw * $i / ($n - 1));
|
|
|
+ };
|
|
|
+ $yAt = static function (int $v) use ($padT, $gh, $maxY): float {
|
|
|
+ return $padT + $gh - ($v / $maxY) * $gh;
|
|
|
+ };
|
|
|
+ $lineWithDots = static function (array $vals, string $stroke, string $dash, float $sw = 1.6) use ($xAt, $yAt, $n): string {
|
|
|
+ $pts = [];
|
|
|
+ $circles = '';
|
|
|
+ for ($i = 0; $i < $n; $i++) {
|
|
|
+ $x = $xAt($i);
|
|
|
+ $y = $yAt((int) $vals[$i]);
|
|
|
+ $pts[] = round($x, 1).','.round($y, 1);
|
|
|
+ $circles .= sprintf(
|
|
|
+ '<circle cx="%.1f" cy="%.1f" r="3.2" fill="%s" stroke="#fff" stroke-width="1"/>',
|
|
|
+ $x,
|
|
|
+ $y,
|
|
|
+ $stroke
|
|
|
+ );
|
|
|
+ }
|
|
|
+ $poly = implode(' ', $pts);
|
|
|
+ $dashAttr = $dash !== '' ? ' stroke-dasharray="'.$dash.'"' : '';
|
|
|
+
|
|
|
+ return '<polyline fill="none" stroke="'.$stroke.'" stroke-width="'.$sw.'"'.$dashAttr.' points="'.$poly.'" />'.$circles;
|
|
|
+ };
|
|
|
+
|
|
|
+ $xAxisY = $padT + $gh;
|
|
|
+ $tickTxt = '';
|
|
|
for ($i = 0; $i < $n; $i++) {
|
|
|
$x = $xAt($i);
|
|
|
- $y = $yAt((int) $vals[$i]);
|
|
|
- $pts[] = round($x, 1).','.round($y, 1);
|
|
|
- $circles .= sprintf(
|
|
|
- '<circle cx="%.1f" cy="%.1f" r="3.2" fill="%s" stroke="#fff" stroke-width="1"/>',
|
|
|
+ $lab = htmlspecialchars((string) ($labels[$i] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
|
+ $tickTxt .= sprintf(
|
|
|
+ '<text x="%.1f" y="%d" text-anchor="middle" font-size="8.5" fill="#374151" font-family="sun-exta,sans-serif">%s</text>',
|
|
|
$x,
|
|
|
- $y,
|
|
|
- $stroke
|
|
|
+ $H - 8,
|
|
|
+ $lab
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- $poly = implode(' ', $pts);
|
|
|
- $dashAttr = $dash !== '' ? ' stroke-dasharray="'.$dash.'"' : '';
|
|
|
+ $yTick = '';
|
|
|
+ for ($k = 0; $k <= 4; $k++) {
|
|
|
+ $v = (int) round($maxY * $k / 4);
|
|
|
+ $y = $yAt($v);
|
|
|
+ $yTick .= sprintf(
|
|
|
+ '<line x1="%d" y1="%.1f" x2="%d" y2="%.1f" stroke="#e5e7eb" stroke-width="1"/>',
|
|
|
+ $padL,
|
|
|
+ $y,
|
|
|
+ $padL + $gw,
|
|
|
+ $y
|
|
|
+ );
|
|
|
+ $yTick .= sprintf(
|
|
|
+ '<text x="%d" y="%.1f" font-size="7.5" fill="#6b7280" font-family="sun-exta,sans-serif">%d</text>',
|
|
|
+ 2,
|
|
|
+ $y + 3,
|
|
|
+ $v
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- return '<polyline fill="none" stroke="'.$stroke.'" stroke-width="'.$sw.'"'.$dashAttr.' points="'.$poly.'" />'.$circles;
|
|
|
- };
|
|
|
+ $legend = '<g font-family="sun-exta,sans-serif" font-size="8.5">';
|
|
|
+ $lx = max($padL + 4, $padL + $gw - 118);
|
|
|
+ $ly = $padT + 2;
|
|
|
+ $items = [
|
|
|
+ [$strokeCur, $legCur, ''],
|
|
|
+ [$strokePrev, $legPrev, '6,4'],
|
|
|
+ ];
|
|
|
+ foreach ($items as $idx => $it) {
|
|
|
+ $yy = $ly + $idx * 12;
|
|
|
+ $legend .= sprintf(
|
|
|
+ '<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="2" %s/>',
|
|
|
+ $lx,
|
|
|
+ $yy,
|
|
|
+ $lx + 16,
|
|
|
+ $yy,
|
|
|
+ $it[0],
|
|
|
+ $it[2] !== '' ? 'stroke-dasharray="'.$it[2].'"' : ''
|
|
|
+ );
|
|
|
+ $legend .= sprintf(
|
|
|
+ '<text x="%d" y="%d" fill="#111">%s</text>',
|
|
|
+ $lx + 20,
|
|
|
+ $yy + 4,
|
|
|
+ htmlspecialchars($it[1], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
|
|
|
+ );
|
|
|
+ }
|
|
|
+ $legend .= '</g>';
|
|
|
|
|
|
- $xAxisY = $padT + $gh;
|
|
|
- $tickTxt = '';
|
|
|
- for ($i = 0; $i < $n; $i++) {
|
|
|
- $x = $xAt($i);
|
|
|
- $lab = htmlspecialchars((string) ($labels[$i] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
|
- $tickTxt .= sprintf(
|
|
|
- '<text x="%.1f" y="%d" text-anchor="middle" font-size="9" fill="#374151" font-family="sun-exta,sans-serif">%s</text>',
|
|
|
- $x,
|
|
|
- $H - 10,
|
|
|
- $lab
|
|
|
+ $svg = sprintf(
|
|
|
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %d %d" width="100%%" height="auto" style="max-width:100%%;">',
|
|
|
+ $W,
|
|
|
+ $H
|
|
|
);
|
|
|
- }
|
|
|
+ $svg .= sprintf('<rect x="0" y="0" width="%d" height="%d" fill="#fafafa"/>', $W, $H);
|
|
|
+ $svg .= $yTick;
|
|
|
+ $svg .= sprintf('<line x1="%d" y1="%.1f" x2="%.1f" y2="%.1f" stroke="#9ca3af" stroke-width="1"/>', $padL, $xAxisY, $padL + $gw, $xAxisY);
|
|
|
+ $svg .= $lineWithDots($prev, $strokePrev, '6,4', 1.4);
|
|
|
+ $svg .= $lineWithDots($cur, $strokeCur, '', 1.8);
|
|
|
+ $svg .= $tickTxt;
|
|
|
+ $svg .= $legend;
|
|
|
+ $svg .= '</svg>';
|
|
|
+
|
|
|
+ return $svg;
|
|
|
+ };
|
|
|
|
|
|
- $yTick = '';
|
|
|
- for ($k = 0; $k <= 4; $k++) {
|
|
|
- $v = (int) round($maxY * $k / 4);
|
|
|
- $y = $yAt($v);
|
|
|
- $yTick .= sprintf(
|
|
|
- '<line x1="%d" y1="%.1f" x2="%d" y2="%.1f" stroke="#e5e7eb" stroke-width="1"/>',
|
|
|
- $padL,
|
|
|
- $y,
|
|
|
- $padL + $gw,
|
|
|
- $y
|
|
|
- );
|
|
|
- $yTick .= sprintf(
|
|
|
- '<text x="%d" y="%.1f" font-size="8" fill="#6b7280" font-family="sun-exta,sans-serif">%d</text>',
|
|
|
- 4,
|
|
|
- $y + 3,
|
|
|
- $v
|
|
|
- );
|
|
|
- }
|
|
|
+ $left = $halfSvg($labels, $pc, $pp, '#2563eb', '#93c5fd', '学案·本周期', '学案·上周期');
|
|
|
+ $right = $halfSvg($labels, $ac, $ap, '#ea580c', '#fdba74', '学情·本周期', '学情·上周期');
|
|
|
|
|
|
- $legend = '<g font-family="sun-exta,sans-serif" font-size="9">';
|
|
|
- $lx = $padL + $gw - 168;
|
|
|
- $ly = $padT + 4;
|
|
|
- $items = [
|
|
|
- ['#2563eb', '学案·本周期', ''],
|
|
|
- ['#ea580c', '学情·本周期', ''],
|
|
|
- ['#93c5fd', '学案·上周期', '6,4'],
|
|
|
- ['#fdba74', '学情·上周期', '6,4'],
|
|
|
- ];
|
|
|
- foreach ($items as $idx => $it) {
|
|
|
- $yy = $ly + $idx * 13;
|
|
|
- $legend .= sprintf(
|
|
|
- '<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="2" %s/>',
|
|
|
- $lx,
|
|
|
- $yy,
|
|
|
- $lx + 18,
|
|
|
- $yy,
|
|
|
- $it[0],
|
|
|
- $it[2] !== '' ? 'stroke-dasharray="'.$it[2].'"' : ''
|
|
|
- );
|
|
|
- $legend .= sprintf(
|
|
|
- '<text x="%d" y="%d" fill="#111">%s</text>',
|
|
|
- $lx + 24,
|
|
|
- $yy + 4,
|
|
|
- htmlspecialchars($it[1], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
|
|
|
- );
|
|
|
- }
|
|
|
- $legend .= '</g>';
|
|
|
-
|
|
|
- $svg = sprintf(
|
|
|
- '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %d %d" width="100%%" height="auto" style="max-width:520px;">',
|
|
|
- $W,
|
|
|
- $H
|
|
|
- );
|
|
|
- $svg .= sprintf('<rect x="0" y="0" width="%d" height="%d" fill="#fafafa"/>', $W, $H);
|
|
|
- $svg .= $yTick;
|
|
|
- $svg .= sprintf('<line x1="%d" y1="%.1f" x2="%.1f" y2="%.1f" stroke="#9ca3af" stroke-width="1"/>', $padL, $xAxisY, $padL + $gw, $xAxisY);
|
|
|
- $svg .= $lineWithDots($pp, '#93c5fd', '6,4', 1.4);
|
|
|
- $svg .= $lineWithDots($ap, '#fdba74', '6,4', 1.4);
|
|
|
- $svg .= $lineWithDots($pc, '#2563eb', '', 1.8);
|
|
|
- $svg .= $lineWithDots($ac, '#ea580c', '', 1.8);
|
|
|
- $svg .= $tickTxt;
|
|
|
- $svg .= $legend;
|
|
|
- $svg .= '</svg>';
|
|
|
-
|
|
|
- return $svg;
|
|
|
+ return '<table class="weekly-chart-pair" style="width:100%;border-collapse:collapse;margin:0;"><tr>'
|
|
|
+ .'<td style="width:50%;vertical-align:top;padding:2px 5px 2px 0;">'.$left.'</td>'
|
|
|
+ .'<td style="width:50%;vertical-align:top;padding:2px 0 2px 5px;">'.$right.'</td>'
|
|
|
+ .'</tr></table>';
|
|
|
};
|
|
|
|
|
|
-$chartSvg = $buildChartSvg($curDaily, $prevDaily);
|
|
|
+$chartSvg = $buildDualChartsHtml($curDaily, $prevDaily);
|
|
|
|
|
|
echo "## 老师组卷与学情分析(近7天)\n\n";
|
|
|
echo "> 生成 {$generatedAt} · {$tz} · 本 {$windowCur} · 上 {$windowPrev}\n\n";
|
|
|
|
|
|
echo "### 总量\n\n";
|
|
|
echo "| 指标 | 本周期 | 上周期 | 环比 |\n";
|
|
|
-echo "| --- | ---: | ---: | --- |\n";
|
|
|
-echo sprintf("| 组卷总套数 | %d | %d | %s |\n", $totalPapersCur, $totalPapersPrev, $wowLineHtml($totalPapersCur, $totalPapersPrev));
|
|
|
-echo sprintf("| 学情分析套数(卷去重) | %d | %d | %s |\n", $totalAnalysisCur, $totalAnalysisPrev, $wowLineHtml($totalAnalysisCur, $totalAnalysisPrev));
|
|
|
-echo sprintf("| 有组卷老师数 | %d | %d | %s |\n", $teachersCur, $teachersPrev, $wowLineHtml($teachersCur, $teachersPrev));
|
|
|
+echo "| :---: | :---: | :---: | :---: |\n";
|
|
|
+echo sprintf("| 组卷总套数 | %d | %d | %s |\n", $totalPapersCur, $totalPapersPrev, $wowLineHtml($totalPapersCur, $totalPapersPrev, $colorPapers));
|
|
|
+echo sprintf("| 学情分析套数(卷去重) | %d | %d | %s |\n", $totalAnalysisCur, $totalAnalysisPrev, $wowLineHtml($totalAnalysisCur, $totalAnalysisPrev, $colorAnalysis));
|
|
|
+echo sprintf("| 有组卷老师数 | %d | %d | %s |\n", $teachersCur, $teachersPrev, $wowLineHtml($teachersCur, $teachersPrev, $colorStudents));
|
|
|
|
|
|
-echo "\n### 近7段每日对比(本周期与上周期时间轴对齐)\n\n";
|
|
|
+echo "\n### 近7段每日对比(时间轴对齐;左:学案 · 右:学情)\n\n";
|
|
|
echo '<div class="weekly-chart">';
|
|
|
echo $chartSvg;
|
|
|
echo "</div>\n\n";
|
|
|
|
|
|
-echo "### 按老师\n\n";
|
|
|
-
|
|
|
-echo sprintf("本周期有组卷 **%d** 人。\n\n", count($rows));
|
|
|
+echo sprintf("### 按老师 本周期有组卷 **%d** 人。\n\n", count($rows));
|
|
|
|
|
|
echo '<table class="weekly-teacher-table">';
|
|
|
echo '<colgroup>';
|
|
|
@@ -459,13 +477,13 @@ foreach ($rows as $r) {
|
|
|
$stuC = (int) ($studentUnionCurMap[$tidKey] ?? 0);
|
|
|
$stuP = (int) ($studentUnionPrevMap[$tidKey] ?? 0);
|
|
|
echo '<tr>';
|
|
|
- echo '<td style="text-align:right">'.((string) $i++).'</td>';
|
|
|
+ echo '<td>'.((string) $i++).'</td>';
|
|
|
echo '<td class="td-name">'.$nameWithId.'</td>';
|
|
|
- echo '<td style="text-align:right" class="td-slash" title="本周期 / 上周期:组卷套数">'.$slashPairGreenHtml($pc, $pp).'</td>';
|
|
|
- echo '<td>'.$compareCellHtml($pc, $pp).'</td>';
|
|
|
- echo '<td style="text-align:right" class="td-slash" title="本周期 / 上周期:学情分析套数(卷去重)">'.$slashPairGreenHtml($ac, $ap).'</td>';
|
|
|
- echo '<td>'.$compareCellHtml($ac, $ap).'</td>';
|
|
|
- echo '<td style="text-align:right" class="td-slash td-stu" title="本周期 / 上周期:组卷∪学情学生合并去重">'.$slashPairGreenHtml($stuC, $stuP).'</td>';
|
|
|
+ echo '<td class="td-slash td-slash-papers" title="本周期 / 上周期:组卷套数">'.$slashPairAccentHtml($pc, $pp, $colorPapers).'</td>';
|
|
|
+ echo '<td class="td-wow-papers">'.$compareCellHtml($pc, $pp, $colorPapers).'</td>';
|
|
|
+ echo '<td class="td-slash td-slash-analysis" title="本周期 / 上周期:学情分析套数(卷去重)">'.$slashPairAccentHtml($ac, $ap, $colorAnalysis).'</td>';
|
|
|
+ echo '<td class="td-wow-analysis">'.$compareCellHtml($ac, $ap, $colorAnalysis).'</td>';
|
|
|
+ echo '<td class="td-slash td-slash-stu td-stu" title="本周期 / 上周期:组卷∪学情学生合并去重">'.$slashPairAccentHtml($stuC, $stuP, $colorStudents).'</td>';
|
|
|
echo "</tr>\n";
|
|
|
}
|
|
|
echo "</tbody></table>\n";
|