const $ = (selector, root=document) => root.querySelector(selector);
// 自己转义引号:textContent→innerHTML 只处理 & < >,插进 attr="…" 时引号会被用来逃逸出属性。
const escapeHtml = value => String(value??'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,''');
let currentPage=1;
async function getJson(url) {
const response=await fetch(url); const data=await response.json();
if(!response.ok) throw new Error(data.message||'数据加载失败'); return data;
}
async function loadPublicData(page=1) {
currentPage=page; const body=$('#publicRows'); body.innerHTML='
| 正在加载数据… |
';
const q=encodeURIComponent($('#publicSearch').value.trim()), category=encodeURIComponent($('#publicCategory').value);
try {
const result=await getJson(`/api/public/dashboard?page=${page}&page_size=20&q=${q}&category=${category}`);
$('#statTotal').textContent=result.stats.total; $('#statCategories').textContent=result.stats.category_count;
$('#statRecent').textContent=result.stats.recent_count; $('#statClarity').textContent=result.stats.average_clarity??'—';
const select=$('#publicCategory'), selected=select.value;
select.innerHTML=''+result.categories.map(x=>``).join(''); select.value=selected;
body.innerHTML=result.data.length?result.data.map(row=>`| ${escapeHtml(row.experiment_id)} | ${row.test_date} | ${escapeHtml(row.material_category)} | ${escapeHtml(row.material_name)} | ${row.material_thickness} mm | ${row.actual_output_power} W | ${row.scanning_speed} mm/s | ${row.is_cut_through?'是':'否'} | ${row.pattern_clarity_score} | |
`).join(''):'| 未找到符合条件的数据 |
';
body.querySelectorAll('tr[data-id]').forEach(row=>row.onclick=()=>showDetail(row.dataset.id));
const pages=Math.max(1,Math.ceil(result.pagination.total/result.pagination.page_size));
$('#publicPager').innerHTML=`第 ${page} / ${pages} 页,共 ${result.pagination.total} 条`;
$('#publicPager').querySelectorAll('button:not([disabled])').forEach(button=>button.onclick=()=>loadPublicData(Number(button.dataset.page)));
} catch(error) { body.innerHTML=`| ${escapeHtml(error.message)} |
`; }
}
const labels={
apparent_density:'表观密度 (g/cm³)',uv_absorption_355nm:'355nm 紫外吸收率 (%)',material_thickness:'材料厚度 (mm)',moisture_content:'含水率 (%)',thermal_conductivity:'热导率 (W/(m·K))',initial_decomposition_temp:'初始分解温度 (℃)',melting_vaporization_temp:'熔点 / 气化温度 (℃)',specific_heat_capacity:'比热容 (J/(kg·K))',carbon_residue_rate:'残炭率 (%)',surface_roughness:'表面粗糙度 (μm)',hardness:'硬度 (邵氏/HV)',actual_output_power:'实际输出功率 (W)',display_current:'设备显示电流 (A)',scanning_speed:'扫描速度 (mm/s)',pulse_frequency:'脉冲频率 (kHz)',pulse_width:'脉冲脉宽 (μs)',defocus_amount:'离焦量 (mm)',scan_line_spacing:'扫描线间距 (mm)',filling_method:'填充方式',processing_size:'加工尺寸',is_cut_through:'是否切透',carbonized_edge_width:'碳化边宽度 (mm)',etching_depth:'蚀刻深度 (μm)',is_fire_smolder:'是否起火 / 阴燃',pattern_clarity_score:'图案清晰度评分',presentation_balance_score:'呈现均衡性评分'
};
const physical=['apparent_density','uv_absorption_355nm','material_thickness','moisture_content','thermal_conductivity','initial_decomposition_temp','melting_vaporization_temp','specific_heat_capacity','carbon_residue_rate','surface_roughness','hardness'];
const laser=['actual_output_power','display_current','scanning_speed','pulse_frequency','pulse_width','defocus_amount','scan_line_spacing','filling_method','processing_size'];
const resultFields=['is_cut_through','carbonized_edge_width','etching_depth','is_fire_smolder','pattern_clarity_score','presentation_balance_score'];
function renderGroup(title,fields,data){return `${title}
${fields.map(key=>{let value=data[key];if(typeof value==='boolean')value=value?'是':'否';if(value===null||value==='')value='未填写';return `
${labels[key]}${escapeHtml(value)}
`}).join('')}
`;}
function renderCustomGroup(values){if(!values?.length)return '';return `扩展数据
${values.map(item=>`
${item.is_active?'':`历史字段 · `}${escapeHtml(item.label)}${escapeHtml(item.display_value||item.raw_value||'未填写')}${item.compatible===false?'历史格式':''}
`).join('')}
`;}
function renderImageGroup(images){return `成品图片
${ImageViewer.galleryHtml(images)}
`;}
async function showDetail(id){
try{const {data}=await getJson(`/api/public/records/${id}`);$('#publicModalContent').innerHTML=`EXPERIMENT DETAIL
${escapeHtml(data.experiment_id)}
${escapeHtml(data.test_date)} · ${escapeHtml(data.material_category)} · ${escapeHtml(data.material_name)}
${renderImageGroup(data.images)}${renderGroup('材料物理特性',physical,data)}${renderGroup('激光雕刻参数',laser,data)}${renderGroup('实验结果',resultFields,data)}${renderCustomGroup(data.custom_fields)}
`;ImageViewer.wire($('#publicModalContent'),data.images);$('#publicModal').hidden=false;}catch(error){alert(error.message);}
}
document.querySelectorAll('[data-close]').forEach(node=>node.onclick=()=>$('#publicModal').hidden=true);
$('#publicSearchBtn').onclick=()=>loadPublicData(1); $('#publicSearch').addEventListener('keydown',event=>{if(event.key==='Enter')loadPublicData(1)}); $('#publicCategory').onchange=()=>loadPublicData(1);
loadPublicData();