激光材料数据平台 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 @@
|
||||
<section class="form-section"><div class="form-section-head"><span>01</span><div><h3>实验与材料</h3><p>用于识别本次测试和材料样品</p></div></div><div class="form-grid">
|
||||
<label>实验 ID <em>*</em><input name="experiment_id" maxlength="100" placeholder="例如 LAS-2026-001" required></label>
|
||||
<label>测试日期 <em>*</em><input type="date" name="test_date" required></label>
|
||||
<label>材料大类 <em>*</em><input name="material_category" maxlength="100" placeholder="例如 木材" required></label>
|
||||
<label>材料名称 / 型号 <em>*</em><input name="material_name" maxlength="200" placeholder="例如 椴木板 A3" required></label>
|
||||
<label>材料厚度 (mm) <em>*</em><input type="number" name="material_thickness" min="0.000001" step="any" required></label>
|
||||
</div></section>
|
||||
<section class="form-section optional-section"><div class="form-section-head"><span>02</span><div><h3>材料物理特性 <b>均为可选</b></h3><p>未测试的物性参数可以留空</p></div></div><div class="form-grid">
|
||||
<label>表观密度 (g/cm³) <small>可选</small><input type="number" name="apparent_density" min="0" step="any"></label>
|
||||
<label>355nm 紫外吸收率 (%) <small>可选</small><input type="number" name="uv_absorption_355nm" min="0" max="100" step="any"></label>
|
||||
<label>含水率 (%) <small>可选</small><input type="number" name="moisture_content" min="0" max="100" step="any"></label>
|
||||
<label>热导率 (W/(m·K)) <small>可选</small><input type="number" name="thermal_conductivity" min="0" step="any"></label>
|
||||
<label>初始分解温度 (℃) <small>可选</small><input type="number" name="initial_decomposition_temp" step="any"></label>
|
||||
<label>熔点 / 气化温度 (℃) <small>可选</small><input type="number" name="melting_vaporization_temp" step="any"></label>
|
||||
<label>比热容 (J/(kg·K)) <small>可选</small><input type="number" name="specific_heat_capacity" min="0" step="any"></label>
|
||||
<label>残炭率 (%) <small>可选</small><input type="number" name="carbon_residue_rate" min="0" max="100" step="any"></label>
|
||||
<label>表面粗糙度 (μm) <small>可选</small><input type="number" name="surface_roughness" min="0" step="any"></label>
|
||||
<label>硬度 (邵氏/HV) <small>可选</small><input type="number" name="hardness" min="0" step="any"></label>
|
||||
</div></section>
|
||||
<section class="form-section"><div class="form-section-head"><span>03</span><div><h3>激光雕刻参数</h3><p>设备输出与扫描设置</p></div></div><div class="form-grid">
|
||||
<label>实际输出功率 (W) <em>*</em><input type="number" name="actual_output_power" min="0" step="any" required></label>
|
||||
<label>设备显示电流 (A) <em>*</em><input type="number" name="display_current" min="0" step="any" required></label>
|
||||
<label>扫描速度 (mm/s) <em>*</em><input type="number" name="scanning_speed" min="0.000001" step="any" required></label>
|
||||
<label>脉冲频率 (kHz) <em>*</em><input type="number" name="pulse_frequency" min="0" step="any" required></label>
|
||||
<label>脉冲脉宽 (μs) <em>*</em><input type="number" name="pulse_width" min="0" step="any" required></label>
|
||||
<label>离焦量 (mm) <em>*</em><input type="number" name="defocus_amount" step="any" required></label>
|
||||
<label>扫描线间距 (mm) <em>*</em><input type="number" name="scan_line_spacing" min="0.000001" step="any" required></label>
|
||||
<label>填充方式 <em>*</em><input name="filling_method" maxlength="100" placeholder="例如 双向填充" required></label>
|
||||
<label>加工尺寸 (mm×mm) <em>*</em><input name="processing_size" maxlength="100" placeholder="例如 20×20" required></label>
|
||||
</div></section>
|
||||
<section class="form-section"><div class="form-section-head"><span>04</span><div><h3>实验结果</h3><p>加工结果、质量评分与备注</p></div></div><div class="form-grid">
|
||||
<label>是否切透 <em>*</em><select name="is_cut_through" required><option value="">请选择</option><option value="true">是(1)</option><option value="false">否(0)</option></select></label>
|
||||
<label>碳化边宽度 (mm) <em>*</em><input type="number" name="carbonized_edge_width" min="0" step="any" required></label>
|
||||
<label>蚀刻深度 (μm) <em>*</em><input type="number" name="etching_depth" min="0" step="any" required></label>
|
||||
<label>是否起火 / 阴燃 <em>*</em><select name="is_fire_smolder" required><option value="">请选择</option><option value="true">是(1)</option><option value="false">否(0)</option></select></label>
|
||||
<label>图案清晰度评分 (0-10) <em>*</em><input type="number" name="pattern_clarity_score" min="0" max="10" step="0.1" required></label>
|
||||
<label>呈现均衡性评分 (0-10) <em>*</em><input type="number" name="presentation_balance_score" min="0" max="10" step="0.1" required></label>
|
||||
<label>成品图文件名 <em>*</em><input name="finished_image_filename" maxlength="255" placeholder="例如 LAS-2026-001.jpg" required></label>
|
||||
<label class="span-2">备注 <em>*</em><textarea name="remarks" maxlength="5000" rows="4" placeholder="记录实验现象或其他说明" required></textarea></label>
|
||||
</div></section>
|
||||
<section class="form-section"><div class="form-section-head"><span>05</span><div><h3>成品图片 <b>可选</b></h3><p>可从相册选择或直接拍照,最多 6 张,提交后可在详情中放大查看</p></div></div><div class="image-uploader" id="recordImages"></div></section>
|
||||
<section class="form-section custom-fields-section" id="customFieldsSection" hidden><div class="form-section-head"><span>06</span><div><h3>扩展数据</h3><p>由管理员配置的附加数据字段</p></div></div><div class="form-grid" id="customFieldsGrid"></div></section>
|
||||
@@ -0,0 +1,58 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{ app_name }}</title><link rel="stylesheet" href="/static/app.css"></head>
|
||||
<body class="app-body" data-role="{{ user.role }}" data-csrf="{{ csrf_token }}">
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="logo"><div class="brand-mark">L</div><div><strong>LASER DATA</strong><span>材料数据管理平台</span></div></div>
|
||||
<nav>
|
||||
<button class="nav-item active" data-view="overview"><i>⌂</i><span>数据概览</span></button>
|
||||
{% if user.role in ['admin','uploader'] %}<button class="nav-item" data-view="entry"><i>+</i><span>手动录入</span></button>{% endif %}
|
||||
<button class="nav-item" data-view="records"><i>▤</i><span>数据中心</span></button>
|
||||
{% if user.role == 'admin' %}
|
||||
<p class="nav-label">系统管理</p>
|
||||
<button class="nav-item" data-view="fields"><i>⌘</i><span>数据字段配置</span></button>
|
||||
<button class="nav-item" data-view="users"><i>♙</i><span>用户与权限</span></button>
|
||||
<button class="nav-item" data-view="backup"><i>⟳</i><span>备份管理</span></button>
|
||||
<button class="nav-item" data-view="audit"><i>⌁</i><span>审计日志</span></button>
|
||||
{% endif %}
|
||||
</nav>
|
||||
<div class="side-help"><b>需要帮助?</b><span>查看操作手册和接口文档</span><div><a href="/docs" target="_blank">API 文档</a><a href="https://github.com" hidden>文档</a></div></div>
|
||||
<div class="side-user"><div class="avatar">{{ user.display_name[0] }}</div><div><b>{{ user.display_name }}</b><span>{{ {'admin':'系统管理员','uploader':'数据录入员','viewer':'只读访客'}[user.role] }}</span></div><button id="logoutBtn" title="退出">↪</button></div>
|
||||
</aside>
|
||||
<div class="mobile-bar"><button id="menuBtn">☰</button><div class="logo"><div class="brand-mark">L</div><strong>LASER DATA</strong></div><div class="avatar">{{ user.display_name[0] }}</div></div>
|
||||
<main class="content">
|
||||
<header class="topbar"><div><p class="eyebrow">MATERIAL INTELLIGENCE</p><h1 id="pageTitle">数据概览</h1></div><div class="top-actions"><span class="status-dot">系统运行正常</span><a class="ghost button-link" href="/" target="_blank">公开看板</a>{% if user.role in ['admin','uploader'] %}<button class="ghost" id="entryKeyBtn">录入 Key</button>{% endif %}<button class="ghost" id="passwordBtn">修改密码</button></div></header>
|
||||
|
||||
<section class="view active" id="view-overview">
|
||||
{% if user.must_change_password %}<div class="notice"><b>首次登录请修改初始密码</b><span>完成密码更新后,此提示将自动消失。</span><button id="noticePasswordBtn">立即修改</button></div>{% endif %}
|
||||
<div class="hero-card"><div><p class="eyebrow">GOOD DATA, BETTER DECISIONS</p><h2>下午好,{{ user.display_name }}</h2><p>每一组可靠的数据,都让下一次雕刻更接近理想结果。</p></div>{% if user.role in ['admin','uploader'] %}<button class="light-button" data-go="entry">录入新数据 <span>↗</span></button>{% endif %}</div>
|
||||
<div class="metric-grid">
|
||||
<article class="metric"><span class="metric-icon coral">▤</span><div><p>数据总量</p><strong>{{ counts.records }}</strong><small>条实验记录</small></div></article>
|
||||
<article class="metric"><span class="metric-icon blue">◫</span><div><p>录入次数</p><strong>{{ counts.batches }}</strong><small>次成功保存</small></div></article>
|
||||
<article class="metric"><span class="metric-icon green">♙</span><div><p>活跃用户</p><strong>{{ counts.users }}</strong><small>个有效账号</small></div></article>
|
||||
</div>
|
||||
<div class="panel-grid"><section class="panel"><div class="panel-head"><div><p class="eyebrow">RECENT ENTRIES</p><h3>最近录入</h3></div><button class="text-button" data-go="records">查看全部 →</button></div><div id="recentRecords" class="loading">正在加载…</div></section>
|
||||
<section class="panel quick-panel"><p class="eyebrow">QUICK START</p><h3>规范录入三步走</h3><ol><li><b>填写材料身份</b><span>实验 ID 必须唯一</span></li><li><b>补充参数与结果</b><span>标记“可选”的物性字段可留空</span></li><li><b>校验并保存</b><span>系统自动检查格式并记录操作者</span></li></ol></section></div>
|
||||
</section>
|
||||
|
||||
<section class="view" id="view-entry">
|
||||
<div class="section-intro"><div><h2 id="entryTitle">手动录入实验数据</h2><p><span class="required-star">*</span> 为必填;标记“可选”的字段可留空。</p></div><button class="secondary" id="resetRecordBtn" type="button">清空表单</button></div>
|
||||
<form id="recordForm" class="record-form">
|
||||
<input type="hidden" id="recordId">
|
||||
{% include "_record_fields.html" %}
|
||||
<div class="form-actions"><span>保存后可在数据中心继续编辑</span><button class="primary" id="saveRecordBtn" type="submit">保存实验数据</button></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="view" id="view-records"><div class="section-intro"><div><h2>数据中心</h2><p>按实验 ID、材料名称或分类快速查找、编辑和管理实验记录。</p></div>{% if user.role in ['admin','uploader'] %}<button class="primary" data-go="entry">+ 新建记录</button>{% endif %}</div><section class="panel"><div class="filters"><input id="recordSearch" placeholder="搜索实验 ID 或材料名称"><input id="categorySearch" placeholder="材料分类(精确)"><button class="primary" id="searchBtn">查询</button></div><div class="table-wrap"><table><thead><tr><th>实验 ID</th><th>测试日期</th><th>材料</th><th>厚度</th><th>功率</th><th>速度</th><th>切透</th><th>清晰度</th><th>操作</th></tr></thead><tbody id="recordRows"></tbody></table></div><div class="pager" id="recordPager"></div></section></section>
|
||||
{% if user.role == 'admin' %}
|
||||
<section class="view" id="view-fields"><div class="section-intro"><div><h2>数据字段配置</h2><p>添加扩展数据种类,设置输入形式、必填规则和公开性;历史值不会因配置修改而删除。</p></div><button class="primary" id="newFieldBtn">+ 添加数据字段</button></div><section class="panel"><div class="table-wrap"><table><thead><tr><th>字段名称</th><th>填写形式</th><th>规则</th><th>公开</th><th>状态</th><th>已有值</th><th>排序</th><th></th></tr></thead><tbody id="fieldRows"></tbody></table></div></section></section>
|
||||
<section class="view" id="view-users"><div class="section-intro"><div><h2>用户与权限</h2><p>管理员负责账号创建、角色分配、停用与密码重置。</p></div><button class="primary" id="newUserBtn">+ 新建用户</button></div><section class="panel"><div class="table-wrap"><table><thead><tr><th>用户</th><th>用户名</th><th>角色</th><th>状态</th><th>创建时间</th><th></th></tr></thead><tbody id="userRows"></tbody></table></div></section></section>
|
||||
<section class="view" id="view-backup"><div class="section-intro"><div><h2>备份管理</h2><p>支持 SQLite 在线一致性备份与 PostgreSQL pg_dump,默认保留 30 天。</p></div><button class="primary" id="backupBtn">⟳ 立即备份</button></div><section class="panel"><div class="table-wrap"><table><thead><tr><th>文件名</th><th>数据库</th><th>大小</th><th>创建人</th><th>创建时间</th><th>SHA-256</th><th></th></tr></thead><tbody id="backupRows"></tbody></table></div></section></section>
|
||||
<section class="view" id="view-audit"><div class="section-intro"><div><h2>审计日志</h2><p>记录登录、录入、修改、删除、账号及备份等关键动作。</p></div></div><section class="panel"><div class="table-wrap"><table><thead><tr><th>时间</th><th>操作者</th><th>动作</th><th>资源</th><th>IP 地址</th><th>详情</th></tr></thead><tbody id="auditRows"></tbody></table></div></section></section>
|
||||
{% endif %}
|
||||
</main>
|
||||
<div class="modal" id="modal" hidden><div class="modal-backdrop"></div><div class="modal-card"><button class="modal-close" data-close>×</button><div id="modalContent"></div></div></div>
|
||||
<div class="toast-stack" id="toasts"></div>
|
||||
<script>window.APP={csrf:document.body.dataset.csrf,role:document.body.dataset.role,mustChange:{{ 'true' if user.must_change_password else 'false' }},entryKeyConfigured:{{ 'true' if user.entry_key_hash else 'false' }}};</script><script src="/static/dynamic-fields.js"></script><script src="/static/image-viewer.js"></script><script src="/static/image-upload.js"></script><script src="/static/app.js"></script>
|
||||
</body></html>
|
||||
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>数据录入 · {{ app_name }}</title><link rel="stylesheet" href="/static/app.css"><link rel="stylesheet" href="/static/public.css"></head>
|
||||
<body class="public-body entry-body" data-authorized="{{ 'true' if entry_authorized else 'false' }}" data-csrf="{{ entry_csrf_token }}">
|
||||
<header class="public-nav"><a class="public-brand" href="/"><span class="brand-mark">L</span><span><b>LASER DATA</b><small>材料实验数据库</small></span></a><nav><a href="/">数据看板</a><a class="active" href="/entry">数据录入</a><a href="/login">管理后台</a></nav></header>
|
||||
<main class="entry-main">
|
||||
<section id="keyGate" class="key-gate" {% if entry_authorized %}hidden{% endif %}>
|
||||
<div class="key-symbol">••••</div><p class="eyebrow">ENTRY ACCESS</p><h1>输入四位录入 Key</h1><p>Key 由用户在管理后台的个人设置中创建</p>
|
||||
<form id="keyForm"><input id="entryKey" inputmode="numeric" pattern="[0-9]{4}" maxlength="4" autocomplete="one-time-code" aria-label="四位录入 Key" required><button class="primary" type="submit">进入录入面板</button><p id="keyError" class="form-error" hidden></p></form>
|
||||
</section>
|
||||
<section id="entryWorkspace" {% if not entry_authorized %}hidden{% endif %}>
|
||||
<div class="entry-heading"><div><p class="eyebrow">NEW EXPERIMENT</p><h1>录入实验数据</h1><p><span class="required-star">*</span> 为必填,材料物理特性可按实际检测情况填写。</p></div><button class="secondary" id="entryLogoutBtn">退出录入</button></div>
|
||||
<form id="publicRecordForm" class="record-form">
|
||||
{% include "_record_fields.html" %}
|
||||
<div class="form-actions"><span>提交前需要再次确认用户名</span><button class="primary" id="entrySubmitBtn" type="submit">检查并提交</button></div>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<div class="modal" id="confirmModal" hidden><div class="modal-backdrop" data-close></div><div class="modal-card"><button class="modal-close" data-close>×</button><p class="eyebrow">FINAL CONFIRMATION</p><h2>确认提交身份</h2><p class="muted">请输入此录入 Key 所属账号的用户名。</p><form id="confirmForm"><label>用户名<input name="username" autocomplete="username" required autofocus></label><button class="primary" type="submit">确认并保存</button></form></div></div>
|
||||
<div class="toast-stack" id="toasts"></div>
|
||||
<script src="/static/dynamic-fields.js"></script><script src="/static/image-viewer.js"></script><script src="/static/image-upload.js"></script><script src="/static/entry.js"></script>
|
||||
</body></html>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>出错了</title><link rel="stylesheet" href="/static/app.css"></head>
|
||||
<body class="center-page"><section class="empty-card"><div class="empty-icon">!</div><h1>操作未完成</h1><p>{{ message }}</p><a class="primary button-link" href="/">返回工作台</a></section></body></html>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>登录 · {{ app_name }}</title><link rel="stylesheet" href="/static/app.css">
|
||||
</head>
|
||||
<body class="login-body">
|
||||
<main class="login-shell">
|
||||
<section class="login-brand">
|
||||
<div class="brand-mark">L</div>
|
||||
<p class="eyebrow">LASER MATERIAL DATA</p>
|
||||
<h1>让每一次激光实验<br>都有据可循</h1>
|
||||
<p>集中管理雕刻参数、材料物性与实验结果,数据可查、操作可审、系统可恢复。</p>
|
||||
<div class="brand-stats"><span><b>32</b> 个标准字段</span><span><b>3</b> 级权限</span><span><b>100%</b> 审计留痕</span></div>
|
||||
</section>
|
||||
<section class="login-card">
|
||||
<div class="mobile-logo"><div class="brand-mark">L</div><strong>{{ app_name }}</strong></div>
|
||||
<p class="eyebrow">WELCOME BACK</p><h2>登录工作台</h2><p class="muted">使用管理员分配的账号继续</p>
|
||||
<form id="loginForm">
|
||||
<label>用户名<input name="username" autocomplete="username" placeholder="请输入用户名" required autofocus></label>
|
||||
<label>密码<input type="password" name="password" autocomplete="current-password" placeholder="请输入密码" required></label>
|
||||
<button class="primary wide" type="submit">安全登录 <span>→</span></button>
|
||||
<p id="loginError" class="form-error" hidden></p>
|
||||
</form>
|
||||
<p class="login-foot">系统采用加密会话与完整审计日志保护数据安全</p>
|
||||
</section>
|
||||
</main>
|
||||
<script>
|
||||
document.querySelector('#loginForm').addEventListener('submit', async (event) => {
|
||||
event.preventDefault(); const button=event.target.querySelector('button'); const error=document.querySelector('#loginError');
|
||||
button.disabled=true; button.firstChild.textContent='登录中… '; error.hidden=true;
|
||||
try { const response=await fetch('/api/auth/login',{method:'POST',body:new FormData(event.target)}); const data=await response.json();
|
||||
if(!response.ok) throw new Error(data.message||'登录失败'); location.href='/admin';
|
||||
} catch(e) { error.textContent=e.message; error.hidden=false; button.disabled=false; button.firstChild.textContent='安全登录 '; }
|
||||
});
|
||||
</script>
|
||||
</body></html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>公开数据 · {{ app_name }}</title><link rel="stylesheet" href="/static/app.css"><link rel="stylesheet" href="/static/public.css"></head>
|
||||
<body class="public-body">
|
||||
<header class="public-nav"><a class="public-brand" href="/"><span class="brand-mark">L</span><span><b>LASER DATA</b><small>材料实验数据库</small></span></a><nav><a class="active" href="/">数据看板</a><a href="/entry">数据录入</a><a href="/login">管理后台</a></nav></header>
|
||||
<main class="public-main">
|
||||
<section class="data-heading"><div><p class="eyebrow">OPEN MATERIAL DATA</p><h1>激光材料实验数据</h1><p>激光雕刻参数、材料物性与加工结果的公开数据视图</p></div><a class="primary button-link" href="/entry">录入实验数据 →</a></section>
|
||||
<section class="public-metrics">
|
||||
<article><span>DATA</span><div><small>实验记录</small><strong id="statTotal">—</strong></div></article>
|
||||
<article><span>TYPE</span><div><small>材料分类</small><strong id="statCategories">—</strong></div></article>
|
||||
<article><span>30D</span><div><small>近 30 天新增</small><strong id="statRecent">—</strong></div></article>
|
||||
<article><span>SCORE</span><div><small>平均清晰度</small><strong id="statClarity">—</strong></div></article>
|
||||
</section>
|
||||
<section class="public-table-panel">
|
||||
<div class="public-panel-head"><div><p class="eyebrow">EXPERIMENT RECORDS</p><h2>实验记录</h2></div><div class="public-filters"><input id="publicSearch" placeholder="实验 ID 或材料名称"><select id="publicCategory"><option value="">全部材料分类</option></select><button id="publicSearchBtn" class="primary">查询</button></div></div>
|
||||
<div class="table-wrap"><table><thead><tr><th>实验 ID</th><th>日期</th><th>材料分类</th><th>材料名称 / 型号</th><th>厚度</th><th>功率</th><th>速度</th><th>切透</th><th>清晰度</th><th></th></tr></thead><tbody id="publicRows"><tr><td colspan="10" class="loading">正在加载数据…</td></tr></tbody></table></div>
|
||||
<div class="pager" id="publicPager"></div>
|
||||
</section>
|
||||
</main>
|
||||
<div class="modal" id="publicModal" hidden><div class="modal-backdrop" data-close></div><div class="modal-card detail-card"><button class="modal-close" data-close>×</button><div id="publicModalContent"></div></div></div>
|
||||
<script src="/static/image-viewer.js"></script><script src="/static/public.js"></script>
|
||||
</body></html>
|
||||
|
||||
Reference in New Issue
Block a user