You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

436 lines
20 KiB

5 months ago
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
1 month ago
<title>“光联万物 创芯未来”首届沪苏协同新兴产业科创大赛报名系统</title>
5 months ago
<link rel="stylesheet" href="../assets/bootstrap.min.css" />
<link rel="stylesheet" href="../assets/prototype.css" />
1 month ago
<script src="../assets/prototype-scale.js?v=20260819-hero"></script>
5 months ago
</head>
<body class="prototype-page admin-desktop-page">
<div class="container-fluid p-3 p-md-4">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
<h5 class="mb-0 section-title">综合看板</h5>
</div>
<div class="row g-3 mb-1" id="kpiGrid"></div>
<div class="row g-3 mt-1 report-main-grid">
<div class="col-lg-6">
<div class="d-flex flex-column gap-3 h-100">
<div class="card report-section-card h-100">
<div class="card-body">
<h6 class="report-section-title">赛道报名结构</h6>
<div id="trackBars"></div>
</div>
</div>
<div class="card report-section-card h-100">
<div class="card-body">
<h6 class="report-section-title">学历结构</h6>
<div id="degreeBars"></div>
</div>
</div>
<div class="card report-section-card h-100">
<div class="card-body">
<h6 class="report-section-title">院校活跃度 TOP5</h6>
<div class="table-responsive">
<table class="table table-sm align-middle mb-0 report-table" id="schoolTopTable">
<thead>
<tr>
<th>排名</th>
<th>院校</th>
<th>报名数</th>
<th>通过率</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="d-flex flex-column gap-3 h-100">
<div class="card report-section-card h-100">
<div class="card-body">
<h6 class="report-section-title">地区分布(国家)</h6>
<div id="countryBars"></div>
<hr class="my-4 report-split-line" />
<h6 class="report-section-title">地区分布(省份TOP6)</h6>
<div id="provinceBars"></div>
</div>
</div>
<div class="card report-section-card h-100">
<div class="card-body">
<h6 class="report-section-title">城市活跃度 TOP5</h6>
<div class="table-responsive">
<table class="table table-sm align-middle mb-0 report-table" id="cityTopTable">
<thead>
<tr>
<th>排名</th>
<th>城市</th>
<th>报名数</th>
<th>通过率</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mt-1">
<div class="col-lg-5">
<div class="card report-section-card h-100">
<div class="card-body">
<h6 class="report-section-title mb-2">赛道评审均分排行</h6>
<div class="table-responsive">
<table class="table table-sm align-middle mb-0 report-table" id="trackScoreRankTable">
<thead>
<tr>
<th>排名</th>
<th>赛道</th>
<th>均分</th>
<th>项目数</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-7">
<div class="card report-section-card h-100">
<div class="card-body">
<div id="trackTop5Groups" class="d-flex flex-column gap-3"></div>
</div>
</div>
</div>
</div>
</div>
<script src="../assets/bootstrap.bundle.min.js"></script>
<script>
(function () {
1 month ago
const seedReportData = [
{ id: "P2026-0001", session: "上海赛区", track: "场景与应用", status: "待评审", degree: "硕士", country: "中国", province: "江苏省", city: "南京市", companyName: "江苏智享科技有限公司", submittedAt: "2026-04-27 10:25", reviewedDays: null },
{ id: "P2026-0002", session: "苏州赛区", track: "材料与设备", status: "已通过", degree: "本科", country: "中国", province: "浙江省", city: "杭州市", companyName: "杭州文旅创新有限公司", submittedAt: "2026-04-26 16:08", reviewedDays: 2 },
{ id: "P2026-0003", session: "深圳赛区", track: "制造与封装", status: "已驳回", degree: "本科", country: "中国", province: "湖北省", city: "武汉市", companyName: "", submittedAt: "2026-04-25 09:40", reviewedDays: 3 },
{ id: "P2026-0004", session: "上海赛区", track: "材料与设备", status: "待评审", degree: "硕士", country: "中国", province: "江苏省", city: "苏州市", companyName: "", submittedAt: "2026-04-24 14:20", reviewedDays: null },
{ id: "P2026-0005", session: "苏州赛区", track: "制造与封装", status: "已通过", degree: "博士", country: "中国", province: "广东省", city: "广州市", companyName: "银发健康科技有限公司", submittedAt: "2026-04-23 11:05", reviewedDays: 2 },
{ id: "P2026-0006", session: "深圳赛区", track: "芯片与组件", status: "待评审", degree: "本科", country: "中国", province: "福建省", city: "厦门市", companyName: "厦门绿色材料有限公司", submittedAt: "2026-04-22 09:30", reviewedDays: null },
{ id: "P2026-0007", session: "上海赛区", track: "制造与封装", status: "已通过", degree: "硕士", country: "中国", province: "江苏省", city: "南京市", companyName: "", submittedAt: "2026-04-21 16:45", reviewedDays: 1 },
{ id: "P2026-0008", session: "苏州赛区", track: "芯片与组件", status: "已驳回", degree: "大专", country: "中国", province: "河南省", city: "郑州市", companyName: "", submittedAt: "2026-04-20 10:10", reviewedDays: 4 },
{ id: "P2026-0009", session: "深圳赛区", track: "场景与应用", status: "已通过", degree: "硕士", country: "中国", province: "上海市", city: "上海市", companyName: "上海灵动AI科技有限公司", submittedAt: "2026-04-19 13:20", reviewedDays: 2 },
{ id: "P2026-0010", session: "上海赛区", track: "材料与设备", status: "待评审", degree: "本科", country: "中国", province: "四川省", city: "成都市", companyName: "成都文创消费运营中心", submittedAt: "2026-04-18 18:10", reviewedDays: null },
{ id: "P2026-0011", session: "苏州赛区", track: "制造与封装", status: "已通过", degree: "本科", country: "海外", province: "", city: "新加坡", companyName: "SeaWellness Pte. Ltd.", submittedAt: "2026-04-17 08:50", reviewedDays: 3 },
{ id: "P2026-0012", session: "深圳赛区", track: "芯片与组件", status: "待评审", degree: "硕士", country: "中国", province: "山东省", city: "青岛市", companyName: "", submittedAt: "2026-04-16 15:35", reviewedDays: null },
{ id: "P2026-0013", session: "上海赛区", track: "场景与应用", status: "已驳回", degree: "本科", country: "中国", province: "江苏省", city: "无锡市", companyName: "", submittedAt: "2026-04-15 12:26", reviewedDays: 5 },
{ id: "P2026-0014", session: "苏州赛区", track: "材料与设备", status: "已通过", degree: "硕士", country: "中国", province: "浙江省", city: "宁波市", companyName: "宁波文旅科技有限公司", submittedAt: "2026-04-14 09:12", reviewedDays: 2 },
{ id: "P2026-0015", session: "深圳赛区", track: "制造与封装", status: "待评审", degree: "博士", country: "中国", province: "北京市", city: "北京市", companyName: "京创生命科技有限公司", submittedAt: "2026-04-13 19:45", reviewedDays: null },
{ id: "P2026-0016", session: "上海赛区", track: "场景与应用", status: "已通过", degree: "本科", country: "海外", province: "", city: "东京", companyName: "AI Retail Lab", submittedAt: "2026-04-12 11:30", reviewedDays: 2 }
5 months ago
];
1 month ago
// 按原有样本结构扩展为 360 条模拟项目,所有看板统计共用这一数据源。
const reportData = seedReportData.flatMap((item, index) => {
const copies = index < 8 ? 23 : 22;
return Array.from({ length: copies }, (_, copyIndex) => ({
...item,
id: item.id + "-" + String(copyIndex + 1).padStart(2, "0"),
sourceId: item.id
}));
});
5 months ago
const els = {
kpiGrid: document.getElementById("kpiGrid"),
trackBars: document.getElementById("trackBars"),
countryBars: document.getElementById("countryBars"),
provinceBars: document.getElementById("provinceBars"),
degreeBars: document.getElementById("degreeBars"),
schoolTopBody: document.querySelector("#schoolTopTable tbody"),
cityTopBody: document.querySelector("#cityTopTable tbody"),
trackScoreRankBody: document.querySelector("#trackScoreRankTable tbody"),
trackTop5Groups: document.getElementById("trackTop5Groups")
};
const reviewScores = {
"P2026-0001": [99, 98, 97, 96],
"P2026-0002": [99, 100, 98, 97],
"P2026-0003": [80, 78, 82, 79],
"P2026-0004": [92, 90, 91, 89],
"P2026-0005": [95, 94, 96, 95],
"P2026-0006": [88, 87, 86, 89],
"P2026-0007": [93, 92, 94, 93],
"P2026-0008": [76, 74, 75, 77],
"P2026-0009": [97, 98, 96, 97],
"P2026-0010": [90, 89, 91, 90],
"P2026-0011": [91, 92, 90, 91],
"P2026-0012": [85, 84, 86, 85],
"P2026-0013": [79, 80, 78, 77],
"P2026-0014": [94, 95, 93, 94],
"P2026-0015": [89, 90, 88, 87],
"P2026-0016": [96, 97, 95, 96]
};
function countBy(arr, keyGetter) {
return arr.reduce((acc, item) => {
const key = keyGetter(item) || "未填写";
acc[key] = (acc[key] || 0) + 1;
return acc;
}, {});
}
function toSortedEntries(obj, limit) {
const entries = Object.entries(obj).sort((a, b) => b[1] - a[1]);
return typeof limit === "number" ? entries.slice(0, limit) : entries;
}
function barGroupHTML(entries, total, emptyText) {
if (!entries.length) return '<p class="text-secondary small mb-0">' + emptyText + "</p>";
return entries.map(([label, count]) => {
const pct = total ? (count / total) * 100 : 0;
return (
'<div class="report-bar-row">' +
'<div class="report-bar-head"><span class="report-bar-label">' + label + '</span><span class="report-bar-meta">' + count + "(" + pct.toFixed(1) + '%)</span></div>' +
'<div class="progress report-progress"><div class="progress-bar" role="progressbar" style="width:' + pct.toFixed(1) + '%"></div></div>' +
"</div>"
);
}).join("");
}
function avgReviewDays(list) {
const reviewed = list.filter((x) => x.reviewedDays !== null && x.reviewedDays !== undefined);
if (!reviewed.length) return "-";
const v = reviewed.reduce((sum, item) => sum + item.reviewedDays, 0) / reviewed.length;
return v.toFixed(1) + " 天";
}
function getFilteredData() {
return reportData;
}
function renderKpis(list) {
const total = list.length;
const approved = list.filter((x) => x.status === "已通过").length;
const pending = list.filter((x) => x.status === "待评审").length;
const passRate = total ? (approved / total) * 100 : 0;
1 month ago
const sessionCounts = countBy(list, (x) => x.session);
5 months ago
const cards = [
1 month ago
{ label: "项目总数", value: total, type: "total" },
{ label: "上海赛区报名", value: sessionCounts["上海赛区"] || 0, type: "session" },
{ label: "苏州赛区报名", value: sessionCounts["苏州赛区"] || 0, type: "session" },
{ label: "深圳赛区报名", value: sessionCounts["深圳赛区"] || 0, type: "session" },
{ label: "待评审数量", value: pending },
{ label: "通过率", value: passRate.toFixed(1) + "%" }
5 months ago
];
els.kpiGrid.innerHTML = cards.map((item) => {
1 month ago
const modifier = item.type ? " report-kpi-card--" + item.type : "";
5 months ago
return (
1 month ago
'<div class="col-12 col-md-6 col-lg-2">' +
'<div class="report-kpi-card' + modifier + '">' +
5 months ago
'<div class="report-kpi-label">' + item.label + "</div>" +
'<div class="report-kpi-value">' + item.value + "</div>" +
"</div>" +
"</div>"
);
}).join("");
}
function renderCityTop(list) {
const groups = {};
list.forEach((item) => {
const city = item.city || "海外";
if (!groups[city]) groups[city] = { total: 0, pass: 0 };
groups[city].total += 1;
if (item.status === "已通过") groups[city].pass += 1;
});
const top = Object.entries(groups).sort((a, b) => b[1].total - a[1].total).slice(0, 5);
if (!top.length) {
els.cityTopBody.innerHTML = '<tr><td colspan="4" class="text-center text-secondary py-3">暂无数据</td></tr>';
return;
}
els.cityTopBody.innerHTML = top.map(([city, v], idx) => {
const rate = v.total ? ((v.pass / v.total) * 100).toFixed(1) + "%" : "-";
return "<tr><td>" + (idx + 1) + "</td><td>" + city + "</td><td>" + v.total + "</td><td>" + rate + "</td></tr>";
}).join("");
}
function getSchoolName(item) {
const schoolMap = {
"P2026-0001": "南京大学",
"P2026-0002": "浙江大学",
"P2026-0003": "华中科技大学",
"P2026-0004": "苏州大学",
"P2026-0005": "中山大学",
"P2026-0006": "厦门大学",
"P2026-0007": "东南大学",
"P2026-0008": "郑州大学",
"P2026-0009": "复旦大学",
"P2026-0010": "四川大学",
"P2026-0011": "新加坡国立大学",
"P2026-0012": "中国海洋大学",
"P2026-0013": "江南大学",
"P2026-0014": "宁波大学",
"P2026-0015": "清华大学",
"P2026-0016": "东京大学"
};
1 month ago
return schoolMap[item.sourceId || item.id] || "未填写";
5 months ago
}
function renderSchoolTop(list) {
const groups = {};
list.forEach((item) => {
const school = getSchoolName(item);
if (!groups[school]) groups[school] = { total: 0, pass: 0 };
groups[school].total += 1;
if (item.status === "已通过") groups[school].pass += 1;
});
const top = Object.entries(groups).sort((a, b) => b[1].total - a[1].total).slice(0, 5);
if (!top.length) {
els.schoolTopBody.innerHTML = '<tr><td colspan="4" class="text-center text-secondary py-3">暂无数据</td></tr>';
return;
}
els.schoolTopBody.innerHTML = top.map(([school, v], idx) => {
const rate = v.total ? ((v.pass / v.total) * 100).toFixed(1) + "%" : "-";
return "<tr><td>" + (idx + 1) + "</td><td>" + school + "</td><td>" + v.total + "</td><td>" + rate + "</td></tr>";
}).join("");
}
1 month ago
function getProjectAvgScore(item) {
const scores = reviewScores[item.sourceId || item.id] || [];
5 months ago
if (!scores.length) return null;
return scores.reduce((sum, n) => sum + n, 0) / scores.length;
}
function renderTrackScoreRank(list) {
const groups = {};
list.forEach((item) => {
1 month ago
const avg = getProjectAvgScore(item);
5 months ago
if (avg === null) return;
if (!groups[item.track]) groups[item.track] = [];
groups[item.track].push(avg);
});
const ranks = Object.entries(groups)
.map(([track, arr]) => ({
track,
avg: arr.reduce((s, n) => s + n, 0) / arr.length,
count: arr.length
}))
.sort((a, b) => b.avg - a.avg);
if (!ranks.length) {
els.trackScoreRankBody.innerHTML = '<tr><td colspan="4" class="text-center text-secondary py-3">暂无评审数据</td></tr>';
return;
}
els.trackScoreRankBody.innerHTML = ranks
.map((r, idx) => {
return "<tr><td>" + (idx + 1) + "</td><td>" + r.track + "</td><td>" + r.avg.toFixed(2) + "</td><td>" + r.count + "</td></tr>";
})
.join("");
}
function renderTrackTop5(list) {
const projectNameMap = {
1 month ago
"P2026-0001": "高速硅光芯片研发平台",
"P2026-0002": "高密度光模块封装方案",
"P2026-0003": "智能光纤传感系统",
5 months ago
"P2026-0004": "数字文旅导览",
"P2026-0005": "银发助餐平台",
"P2026-0006": "绿色包装新材料",
"P2026-0007": "社区健康驿站",
"P2026-0008": "夜经济市集运营",
"P2026-0009": "灵动AI消费助手",
"P2026-0010": "商旅文创运营中台",
1 month ago
"P2026-0011": "海外制造与封装服务",
5 months ago
"P2026-0012": "海洋功能消费品",
"P2026-0013": "零售数字人导购",
"P2026-0014": "港城夜游文旅项目",
"P2026-0015": "智慧生命健康站",
"P2026-0016": "AI Retail Lab"
};
const byTrack = {};
list.forEach((item) => {
1 month ago
const avg = getProjectAvgScore(item);
5 months ago
if (avg === null) return;
if (!byTrack[item.track]) byTrack[item.track] = [];
1 month ago
const sourceId = item.sourceId || item.id;
byTrack[item.track].push({ id: item.id, name: projectNameMap[sourceId] || item.id, avg });
5 months ago
});
const tracks = Object.keys(byTrack);
if (!tracks.length) {
els.trackTop5Groups.innerHTML = '<p class="text-secondary small mb-0">暂无评审数据</p>';
return;
}
const cards = tracks
.sort()
.map((track) => {
const rows = byTrack[track]
.sort((a, b) => b.avg - a.avg)
.slice(0, 3)
.map((item, idx) => {
return (
'<li class="top5-item-row">' +
'<span class="top5-item-rank">' + (idx + 1) + "</span>" +
'<span class="top5-item-name">' + item.name + "</span>" +
'<span class="top5-item-score">' + item.avg.toFixed(2) + "</span>" +
"</li>"
);
})
.join("");
return (
'<div class="col-12 col-md-6">' +
'<div class="top5-track-card h-100">' +
'<div class="top5-track-title">' + track + " TOP3</div>" +
'<ul class="top5-track-list">' + rows + "</ul>" +
"</div>" +
"</div>"
);
})
.join("");
els.trackTop5Groups.innerHTML = '<div class="row g-2">' + cards + "</div>";
}
function render() {
const list = getFilteredData();
const total = list.length;
renderKpis(list);
els.trackBars.innerHTML = barGroupHTML(
toSortedEntries(countBy(list, (x) => x.track)),
total,
"当前筛选条件下暂无赛道数据。"
);
els.countryBars.innerHTML = barGroupHTML(
toSortedEntries(countBy(list, (x) => x.country)),
total,
"当前筛选条件下暂无国家数据。"
);
els.provinceBars.innerHTML = barGroupHTML(
toSortedEntries(countBy(list.filter((x) => x.country === "中国"), (x) => x.province), 6),
total,
"当前筛选条件下暂无省份数据。"
);
els.degreeBars.innerHTML = barGroupHTML(
toSortedEntries(countBy(list, (x) => x.degree)),
total,
"当前筛选条件下暂无学历数据。"
);
renderSchoolTop(list);
renderCityTop(list);
renderTrackScoreRank(list);
renderTrackTop5(list);
}
render();
})();
</script>
</body>
</html>