+124
-13
@@ -20,7 +20,7 @@
|
||||
<p>支持按证书编号、持有人姓名与发证日期进行检索,也可以直接扫码快速定位证书记录。</p>
|
||||
<div class="hero-meta">
|
||||
<span class="meta-chip">数据范围:2024 年 10 月至今</span>
|
||||
<span class="meta-chip">查询方式:文本检索 / 二维码扫描</span>
|
||||
<span class="meta-chip">查询方式:文本检索 / 条形码扫描</span>
|
||||
</div>
|
||||
</div>
|
||||
<aside class="stat-card">
|
||||
@@ -44,8 +44,8 @@
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="searchTerm">编号 / 姓名</label>
|
||||
<div class="input-with-action">
|
||||
<input type="text" id="searchTerm" class="tech-input" placeholder="输入编号或姓名,或点击右侧扫码">
|
||||
<button type="button" class="icon-action" id="startScanBtn" title="扫码查询" aria-label="扫码查询">
|
||||
<input type="text" id="searchTerm" class="tech-input" placeholder="输入编号、姓名或奖项名,或点击右侧扫条形码">
|
||||
<button type="button" class="icon-action" id="startScanBtn" title="扫条形码查询" aria-label="扫条形码查询">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7V5a2 2 0 0 1 2-2h2"></path><path d="M17 3h2a2 2 0 0 1 2 2v2"></path><path d="M21 17v2a2 2 0 0 1-2 2h-2"></path><path d="M7 21H5a2 2 0 0 1-2-2v-2"></path><rect x="7" y="7" width="10" height="10"></rect></svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -80,7 +80,7 @@
|
||||
<div id="reader"></div>
|
||||
<div class="scanner-line"></div>
|
||||
</div>
|
||||
<div class="scanner-text">请将二维码置于识别框内</div>
|
||||
<div class="scanner-text">请将条形码置于识别框内</div>
|
||||
<button class="tech-button secondary" id="stopScanBtn" type="button">退出扫描</button>
|
||||
</div>
|
||||
|
||||
@@ -101,10 +101,24 @@
|
||||
|
||||
const config = {
|
||||
fps: 15,
|
||||
qrbox: { width: 300, height: 180 },
|
||||
qrbox: { width: 320, height: 140 },
|
||||
aspectRatio: 1.0
|
||||
};
|
||||
|
||||
if (typeof Html5QrcodeSupportedFormats !== 'undefined') {
|
||||
config.formatsToSupport = [
|
||||
Html5QrcodeSupportedFormats.CODE_128,
|
||||
Html5QrcodeSupportedFormats.CODE_39,
|
||||
Html5QrcodeSupportedFormats.CODE_93,
|
||||
Html5QrcodeSupportedFormats.EAN_13,
|
||||
Html5QrcodeSupportedFormats.EAN_8,
|
||||
Html5QrcodeSupportedFormats.UPC_A,
|
||||
Html5QrcodeSupportedFormats.UPC_E,
|
||||
Html5QrcodeSupportedFormats.ITF,
|
||||
Html5QrcodeSupportedFormats.CODABAR
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
await html5QrCode.start(
|
||||
{ facingMode: "environment" },
|
||||
@@ -138,7 +152,7 @@
|
||||
const display = document.getElementById('resultsArea');
|
||||
|
||||
if (!term) {
|
||||
alert("请输入证书编号或持有人姓名。");
|
||||
alert("请输入证书编号、持有人姓名或奖项名。");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,7 +160,7 @@
|
||||
|
||||
try {
|
||||
let query = sbClient.from('certificates').select('*');
|
||||
query = query.or(`cert_number.ilike.%${term}%,holder_name.ilike.%${term}%`);
|
||||
query = query.or(`cert_number.ilike.%${term}%,holder_name.ilike.%${term}%,honor_title.ilike.%${term}%`);
|
||||
|
||||
if (start) query = query.gte('issue_date', start);
|
||||
if (end) query = query.lte('issue_date', end);
|
||||
@@ -154,13 +168,105 @@
|
||||
const { data, error } = await query.order('issue_date', { ascending: false });
|
||||
if (error) throw error;
|
||||
|
||||
renderResults(data);
|
||||
const awardRecipientCounts = await loadAwardRecipientCounts(data);
|
||||
renderResults(data, awardRecipientCounts);
|
||||
} catch (err) {
|
||||
display.innerHTML = `<div class="error-state">检索失败:${err.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderResults(data) {
|
||||
async function loadAwardRecipientCounts(data) {
|
||||
const awardTitles = [...new Set((data || []).map(item => item.honor_title).filter(Boolean))];
|
||||
if (awardTitles.length === 0) return {};
|
||||
|
||||
const countPairs = await Promise.all(awardTitles.map(async title => {
|
||||
const { count, error } = await sbClient
|
||||
.from('certificates')
|
||||
.select('id', { count: 'exact', head: true })
|
||||
.eq('honor_title', title);
|
||||
|
||||
if (error) throw error;
|
||||
return [title, count || 0];
|
||||
}));
|
||||
|
||||
return Object.fromEntries(countPairs);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? '').replace(/[&<>"']/g, char => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}[char]));
|
||||
}
|
||||
|
||||
function renderAwardStats(data, awardRecipientCounts) {
|
||||
const holderCounts = (data || []).reduce((counts, item) => {
|
||||
const holderName = item.holder_name || '未填写';
|
||||
counts[holderName] = (counts[holderName] || 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
|
||||
const holderStats = Object.entries(holderCounts)
|
||||
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], 'zh-CN'));
|
||||
const maxHolderCount = Math.max(...holderStats.map(([, count]) => count), 1);
|
||||
|
||||
const awardStats = [...new Set((data || []).map(item => item.honor_title).filter(Boolean))]
|
||||
.map(title => ({
|
||||
title,
|
||||
count: awardRecipientCounts[title] || 0
|
||||
}))
|
||||
.sort((a, b) => b.count - a.count || a.title.localeCompare(b.title, 'zh-CN'));
|
||||
|
||||
const holderBars = holderStats.map(([name, count]) => `
|
||||
<div class="chart-row">
|
||||
<div class="chart-row-label" title="${escapeHtml(name)}">${escapeHtml(name)}</div>
|
||||
<div class="chart-bar-track">
|
||||
<div class="chart-bar-fill" style="width: ${Math.max((count / maxHolderCount) * 100, 6)}%;"></div>
|
||||
</div>
|
||||
<div class="chart-row-value">${count} 项</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
const awardChips = awardStats.map(item => `
|
||||
<div class="award-count-chip">
|
||||
<span class="award-count-title" title="${escapeHtml(item.title)}">${escapeHtml(item.title)}</span>
|
||||
<strong>${item.count}</strong>
|
||||
<span>人获得</span>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
return `
|
||||
<div class="results-summary">
|
||||
<div class="summary-stat-tile">
|
||||
<span>查询结果</span>
|
||||
<strong>${data.length}</strong>
|
||||
</div>
|
||||
<div class="summary-stat-tile">
|
||||
<span>涉及人员</span>
|
||||
<strong>${holderStats.length}</strong>
|
||||
</div>
|
||||
<div class="summary-stat-tile">
|
||||
<span>奖项种类</span>
|
||||
<strong>${awardStats.length}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-grid">
|
||||
<section class="stats-panel">
|
||||
<div class="stats-panel-title">个人奖项数量</div>
|
||||
<div class="bar-chart">${holderBars}</div>
|
||||
</section>
|
||||
<section class="stats-panel">
|
||||
<div class="stats-panel-title">查询奖项获得人数</div>
|
||||
<div class="award-count-grid">${awardChips || '<div class="muted-note">暂无奖项名称</div>'}</div>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderResults(data, awardRecipientCounts = {}) {
|
||||
const display = document.getElementById('resultsArea');
|
||||
if (!data || data.length === 0) {
|
||||
display.innerHTML = '<div class="empty-state">未找到匹配的证书记录。</div>';
|
||||
@@ -168,6 +274,7 @@
|
||||
}
|
||||
|
||||
let html = `
|
||||
${renderAwardStats(data, awardRecipientCounts)}
|
||||
<div class="table-wrapper">
|
||||
<table class="result-table">
|
||||
<thead>
|
||||
@@ -185,10 +292,10 @@
|
||||
data.forEach(item => {
|
||||
html += `
|
||||
<tr>
|
||||
<td class="table-highlight">${item.cert_number || '-'}</td>
|
||||
<td>${item.holder_name || '-'}</td>
|
||||
<td>${item.honor_title || '-'}</td>
|
||||
<td>${item.issue_date || '-'}</td>
|
||||
<td class="table-highlight">${escapeHtml(item.cert_number || '-')}</td>
|
||||
<td>${escapeHtml(item.holder_name || '-')}</td>
|
||||
<td>${escapeHtml(item.honor_title || '-')}</td>
|
||||
<td>${escapeHtml(item.issue_date || '-')}</td>
|
||||
<td><button class="tech-link-button secondary" type="button" onclick="goToDetail('${item.id}')">查看详情</button></td>
|
||||
</tr>
|
||||
`;
|
||||
@@ -211,6 +318,10 @@
|
||||
params.get('cert_number') ||
|
||||
params.get('certNumber') ||
|
||||
params.get('number') ||
|
||||
params.get('awardName') ||
|
||||
params.get('honor_title') ||
|
||||
params.get('honorTitle') ||
|
||||
params.get('award') ||
|
||||
params.get('q') ||
|
||||
'';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user