激光材料数据平台 1.3.0
公开数据看板、四位 Key 录入面板、管理后台与三级权限、动态字段配置、 PostgreSQL/SQLite 双支持、数据库备份,以及本版新增的成品图上传与预览。 图片相关: - 录入表单支持相册选图与手机端直接拍照,每条记录最多 6 张 - 浏览器内压缩到最长边 1600 并剥离 EXIF,入库统一为 JPG/PNG - 二进制直接入库,现有备份自动覆盖图片,部署无需新增卷 - 详情页缩略图宫格与全屏 lightbox,公开看板同样可见 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
const $ = (selector, root=document) => root.querySelector(selector);
|
||||
// 自己转义引号:textContent→innerHTML 只处理 & < >,插进 attr="…" 时引号会被用来逃逸出属性。
|
||||
const escapeHtml = value => String(value??'').replace(/&/g,'&').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='<tr><td colspan="10" class="loading">正在加载数据…</td></tr>';
|
||||
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='<option value="">全部材料分类</option>'+result.categories.map(x=>`<option value="${escapeHtml(x)}">${escapeHtml(x)}</option>`).join(''); select.value=selected;
|
||||
body.innerHTML=result.data.length?result.data.map(row=>`<tr data-id="${row.id}"><td><b>${escapeHtml(row.experiment_id)}</b></td><td>${row.test_date}</td><td>${escapeHtml(row.material_category)}</td><td>${escapeHtml(row.material_name)}</td><td>${row.material_thickness} mm</td><td>${row.actual_output_power} W</td><td>${row.scanning_speed} mm/s</td><td><span class="tag ${row.is_cut_through?'':'off'}">${row.is_cut_through?'是':'否'}</span></td><td>${row.pattern_clarity_score}</td><td><button class="table-action">详情</button></td></tr>`).join(''):'<tr><td colspan="10" class="empty">未找到符合条件的数据</td></tr>';
|
||||
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=`<span>第 ${page} / ${pages} 页,共 ${result.pagination.total} 条</span><button ${page<=1?'disabled':''} data-page="${page-1}">上一页</button><button ${page>=pages?'disabled':''} data-page="${page+1}">下一页</button>`;
|
||||
$('#publicPager').querySelectorAll('button:not([disabled])').forEach(button=>button.onclick=()=>loadPublicData(Number(button.dataset.page)));
|
||||
} catch(error) { body.innerHTML=`<tr><td colspan="10" class="empty">${escapeHtml(error.message)}</td></tr>`; }
|
||||
}
|
||||
|
||||
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 `<section class="detail-group"><h3>${title}</h3><div class="detail-grid">${fields.map(key=>{let value=data[key];if(typeof value==='boolean')value=value?'是':'否';if(value===null||value==='')value='未填写';return `<div><small>${labels[key]}</small><b>${escapeHtml(value)}</b></div>`}).join('')}</div></section>`;}
|
||||
function renderCustomGroup(values){if(!values?.length)return '';return `<section class="detail-group"><h3>扩展数据</h3><div class="detail-grid">${values.map(item=>`<div><small>${item.is_active?'':`历史字段 · `}${escapeHtml(item.label)}</small><b>${escapeHtml(item.display_value||item.raw_value||'未填写')}</b>${item.compatible===false?'<span class="history-badge">历史格式</span>':''}</div>`).join('')}</div></section>`;}
|
||||
function renderImageGroup(images){return `<section class="detail-group"><h3>成品图片</h3><div class="detail-gallery">${ImageViewer.galleryHtml(images)}</div></section>`;}
|
||||
async function showDetail(id){
|
||||
try{const {data}=await getJson(`/api/public/records/${id}`);$('#publicModalContent').innerHTML=`<div class="detail-title"><p class="eyebrow">EXPERIMENT DETAIL</p><h2>${escapeHtml(data.experiment_id)}</h2><p>${escapeHtml(data.test_date)} · ${escapeHtml(data.material_category)} · ${escapeHtml(data.material_name)}</p></div><div class="detail-groups">${renderImageGroup(data.images)}${renderGroup('材料物理特性',physical,data)}${renderGroup('激光雕刻参数',laser,data)}${renderGroup('实验结果',resultFields,data)}${renderCustomGroup(data.custom_fields)}</div>`;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();
|
||||
Reference in New Issue
Block a user