> 本 Skill 固化了我(灵犀)在将 Excel 工程计算表转化为单文件 HTML 过程中积累的所有经验。
将过程工程计算(Excel / VBA / 手工公式)转化为单文件 HTML 应用,具备专业工业 UI、双主题、SVG 可视化图标、实时计算和导出功能。
> ⚠️ 核心原则:发现源文件逻辑错误时,先暂停任务,提醒用户修正原始 Excel,绝不直接在 HTML 中将错就错。
| 信号 | 示例 | 行动 |
|---|---|---|
| ------ | ------ | ------ |
| 计算结果数量级异常 | 孔板数量 = 528 块(正常应为 1~3 块) | 暂停,提示用户检查公式 |
| 单位换算常数可疑 | /10^6 但上下文是 kPa→MPa(应为 /10^3) | 暂停,列出原始公式让用户确认 |
| 与标准公式明显不符 | HG/T 公式应为 √(2ΔP/ρ),实际写的是 ΔP/2ρ | 暂停,给出标准公式对比 |
| 常数值无来源 | 公式里出现 16484 但标准里是 128.45 | 从 XML 提取原始公式,让用户确认 |
⚠️ 发现源文件公式可疑,已暂停转换:
[单元格坐标]:[原始公式]
可疑原因:[具体说明]
正确应为:[标准公式或合理值]
请先修正 Excel 源文件中的公式,保存后告诉我,我再重新提取并继续。
.xlsm 内部结构xl/vbaProject.bin)将 VBA 函数逻辑改写为 JavaScript,注意:
Application.WorksheetFunction → 原生 JS Math API关键函数对照:
| VBA / Excel | JavaScript | 功能 |
|---|---|---|
| ------------ | ------------ | ------ |
Application.Pi() | Math.PI | π 常数 |
Sqr() | Math.sqrt() | 平方根 |
Atn() | Math.atan() | 反正切 |
WorksheetFunction.Acos() | Math.acos() | 反余弦 |
Int() / Fix() | Math.floor() / Math.ceil() | 取整 |
参考 assets/html-template.html 模板,包含:
选择配色方案(见 references/color-schemes.md),并根据需要添加:
references/formulas.mdreferences/color-schemes.mdassets/html-template.html将工程示意图(SVG矢量图标)与数据输入框合并为统一面板,让用户一眼看到设备类型、输入数量、当量参数,无需在两处来回跳转。
已验证参考:D:\RayClaw\PE Calculation\01-Pipelines\不可压缩流体压力降计算-Pipe Pressure Drop-V2.Visual.html
<svg class="valve-svg" viewBox="0 0 100 60" fill="none" xmlns="http://www.w3.org/2000/svg">
fill="none",通过 CSS stroke 和 currentColor 控制颜色| 管件 | SVG 关键特征 | 当量长度 |
|---|---|---|
| ------ | ------------- | --------- |
| 球阀 Ball Valve | 圆形阀体 + 两侧水平管 | 30d |
| 闸阀 Gate Valve | 三角楔形 + 升降杆 | 8d |
| 截止阀 Globe Valve | S 形流道 + 垂直阀杆 | 340d |
| 蝶阀 Butterfly Valve | 圆盘在管道内偏转 | 45d |
| 90°弯头 90° Elbow | L 形弯管 | 30d |
| 45°弯头 45° Elbow | 45° 斜弯管 | 16d |
| 180°弯头 180° Bend | U 形回转弯管 | 50d |
| 三通直通 Tee-Run | T 形,直通方向 | 20d |
| 三通支流 Tee-Branch | T 形,支流方向 | 60d |
| 变径 Reducer | 锥形渐缩/渐扩管 | 自定义 Le |
/* 亮色模式 - 图标颜色 */
.valve-svg { stroke: var(--brand-primary); opacity: 0.7; transition: all 0.3s ease; }
/* 暗色模式 - 霓虹发光效果 */
[data-theme="dark"] .valve-svg { stroke: var(--neon-cyan); opacity: 0.6; }
[data-theme="dark"] .valve-item.active .valve-svg {
stroke: var(--neon-cyan); opacity: 1;
filter: drop-shadow(0 0 6px rgba(0, 229, 255, 0.5));
}
/* 选中/激活状态 */
.valve-item.active .valve-svg { opacity: 1; stroke-width-adjust: 1.2; }
将「设备示意图」和「数量输入框」合并为一个 .valve-item 单元:
<div class="card">
<h3 class="sec-title">管件当量长度 (个数)</h3>
<div class="valve-grid">
<!-- 每个管件一个 valve-item -->
<div class="valve-item" id="valve_ball">
<svg class="valve-svg" viewBox="0 0 100 60">
<!-- 球阀 SVG 路径 -->
</svg>
<div class="valve-name">球阀 Ball Valve</div>
<div class="valve-le">30d</div>
<input type="number" class="valve-input" id="f_ball" value="0" min="0"
oninput="highlightFit(this, 'ball'); triggerRealtime()">
</div>
<!-- 更多管件... -->
</div>
</div>
.valve-grid {
display: grid;
grid-template-columns: repeat(5, 1fr); /* 桌面端5列 */
gap: 12px;
}
/* 平板端 */
@media (max-width: 900px) {
.valve-grid { grid-template-columns: repeat(4, 1fr); }
}
/* 小平板/大手机 */
@media (max-width: 680px) {
.valve-grid { grid-template-columns: repeat(3, 1fr); }
}
/* 手机 */
@media (max-width: 440px) {
.valve-grid { grid-template-columns: repeat(2, 1fr); }
}
.valve-input {
width: 100%;
padding: 7px 4px;
text-align: center;
font-family: var(--font-mono);
font-size: 0.85rem;
background: rgba(0, 0, 0, 0.04);
border: 1px solid var(--bd);
border-radius: 6px;
color: var(--txt-primary);
transition: all 0.25s ease;
}
/* 有值时 - 绿色高亮 */
.valve-input.has-value {
color: var(--ok);
border-color: rgba(0, 230, 118, 0.3);
background: rgba(0, 230, 118, 0.04);
}
[data-theme="dark"] .valve-input {
background: rgba(0, 0, 0, 0.25);
}
[data-theme="dark"] .valve-input.has-value {
color: var(--ok);
border-color: rgba(0, 230, 118, 0.35);
background: rgba(0, 230, 118, 0.06);
}
.valve-le {
font-family: var(--font-mono);
font-size: 0.58rem;
color: var(--txt-muted);
margin-bottom: 8px;
}
.valve-name {
font-size: 0.72rem;
font-weight: 500;
color: var(--txt-secondary);
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
const valveMap = {
'f_ball': 'ball',
'f_gate': 'gate',
'f_globe': 'globe',
'f_butterfly': 'butterfly',
'f_90elbow': '90elbow',
'f_45elbow': '45elbow',
'f_180bend': '180bend',
'f_tee_run': 'tee_run',
'f_tee_branch': 'tee_branch'
};
function updateValveVisual() {
for (const [inputId, valveType] of Object.entries(valveMap)) {
const input = document.getElementById(inputId);
if (!input) continue;
const count = parseInt(input.value) || 0;
const valveItem = document.getElementById('valve_' + valveType);
if (valveItem) {
input.classList.toggle('has-value', count > 0);
if (count > 0) {
valveItem.classList.add('active', 'valve-active');
} else {
valveItem.classList.remove('active', 'valve-active');
}
}
}
}
function highlightFit(el, valveType) {
updateValveVisual();
}
f_* 输入框 ID 在整个 HTML 中只能出现一次(旧版曾出现 SVG 区域和表格中重复 ID 的问题)updateValveVisual() 以恢复默认状态oninput="highlightFit(this, 'xxx'); triggerRealtime()"当需要为其他过程工程场景(安全阀、换热器、泵、容器等)添加 SVG 图标时:
viewBox="0 0 100 60" 标准.valve-item / .valve-grid 同样的响应式布局D:\RayClaw\PE Calculation\01-Pipelines\不可压缩流体压力降计算-Pipe Pressure Drop-Incompressible-Ver2.1.htmlD:\RayClaw\PE Calculation\01-Pipelines\不可压缩流体压力降计算-Pipe Pressure Drop-Incompressible.htmlD:\RayClaw\PE Calculation\01-Pipelines\不可压缩流体压力降计算-Pipe Pressure Drop-V2.Visual.html| 序号 | 名称 | 内容 |
|---|---|---|
| ------ | ------ | ------ |
| ① | 基本参数 Basic Data | 流量输入方式切换(体积/质量)、密度、粘度、管内径 |
| ② | 管道总长度 Pipe Length | 直管段长度、管壁粗糙度(含参考tooltip)、管件当量长度(可视化面板) |
| ③ | 静压降参数 Static Pressure Drop | 进出口标高 Z₁、Z₂ |
| ④ | 速度压降 Velocity Pressure Drop | 进出口截面积、公式说明:ΔPv = ρ(u₂²−u₁²)/2 × 10⁻³ KPa |
| ⑤ | 设备压降 Equipment ΔP | 换热器、流量计、过滤器、其他设备 |
<!-- 模式切换按钮 -->
<div class="mode-toggle">
<button class="mode-btn active" onclick="setMode('vol')">体积流量 qv (m³/h)</button>
<button class="mode-btn" onclick="setMode('mass')">质量流量 qm (kg/h)</button>
</div>
<!-- 体积流量输入 -->
<div id="grpVol">
<label>体积流量 qv (m³/h)</label>
<input type="number" id="qv" oninput="syncFlow('vol')">
</div>
<!-- 质量流量输入 -->
<div id="grpMass" style="display:none">
<label>质量流量 qm (kg/h)</label>
<input type="number" id="qm" oninput="syncFlow('mass')">
</div>
syncFlow() 函数根据密度自动换算.tip-wrap 包裹粗糙度输入框<div class="fg tip-wrap">
<label>管壁粗糙度 ε (mm) <span class="tip-hint">⬡ 参考</span></label>
<input type="number" id="eps" value="0.2">
<div class="tip">
<div class="tip-head">常见管材粗糙度参考值</div>
<table>
<tr><td>碳钢管(无缝)</td><td class="tv" onclick="pickEps(0.2)">0.2</td></tr>
<!-- 更多管材... -->
</table>
</div>
</div>
推荐方案:使用上方的「合并式可视化输入面板」替代传统表格。
| 管件 | 默认值 | 当量长度系数(×d) |
|---|---|---|
| ------ | -------- | ------------------- |
| 球阀 | 0 | 30d |
| 闸阀 | 5 | 8d |
| 截止阀 | 0 | 340d |
| 蝶阀 | 0 | 45d |
| 90°弯头 | 10 | 30d |
| 45°弯头 | 0 | 16d |
| 180°弯头 | 0 | 50d |
| 三通-直通 | 0 | 20d |
| 三通-支流 | 0 | 60d |
| 变径 | 0 | 需单独输入 Le |
crit = 1/(ε/d × Re × √λ) 中断现象:页面功能完全无反应(按钮点击无效果、无报错提示)
根因:CSS 定义了某个组件的样式,但 HTML body 中缺少对应 DOM 元素。JS 在 顶层对查询结果调用方法时抛出 TypeError,导致整个 块被中断,后续所有函数定义全部丢失。
典型案例:
.theme-toggle 样式(含 .sun-icon / .moon-icon 子元素).theme-toggle 元素document.querySelector('.theme-toggle').addEventListener(...) → 返回 null → TypeErrorsetMode()、calc()、toast() 等函数全部未定义修复方案:
// ✅ 正确做法:包裹在 IIFE + null 检查
(function() {
const toggle = document.querySelector('.theme-toggle');
if (toggle) {
toggle.addEventListener('click', () => { /* ... */ });
}
})();
calc() 函数缺少 try-catch现象:计算时报错,但页面无任何提示,用户不知道发生了什么。
修复:给 calc() 包裹 try-catch,错误信息通过 toast 显示:
function calc(){
try {
// ... 计算逻辑 ...
toast('✅ 计算完成');
} catch(e) {
console.error('Calculation error:', e);
toast('⚠️ 计算出错: ' + e.message, true);
}
}
修复:所有 均需绑定 oninput="triggerRealtime()"(详见下方「实时计算模块」章节)
现象:SVG 区域和表格中使用了相同的 f_ball 等 ID,导致 JS 只能操作第一个元素。
修复:
id 在整个 HTML 中唯一.ft-wrap 表格区域(或设为 display:none 隐藏而非删除,避免样式引用断裂)参考 assets/html-template.html 中的实现,为所有计算页面添加实时计算能力。
"REAL TIME CALCULATING 自动计算中..." + 呼吸光效动画/* 按钮文字切换 */
.btn-calc .btn-text { display: inline; }
.btn-calc .btn-live { display: none; }
.btn-calc.live-mode .btn-text { display: none; }
.btn-calc.live-mode .btn-live { display: inline-flex; align-items: center; gap: 6px; }
/* 呼吸光效 */
.btn-calc.live-mode { animation: liveGlow 2s ease-in-out infinite; }
@keyframes liveGlow {
0%, 100% { box-shadow: 0 4px 20px rgba(42, 168, 137, 0.2); }
50% { box-shadow: 0 4px 30px rgba(42, 168, 137, 0.45), 0 0 50px rgba(42, 168, 137, 0.1); }
}
/* 暗色主题呼吸光效 */
[data-theme="dark"] .btn-calc.live-mode { animation-name: liveGlowDark; }
@keyframes liveGlowDark {
0%, 100% { box-shadow: 0 4px 20px rgba(0, 229, 255, 0.15); }
50% { box-shadow: 0 4px 30px rgba(0, 229, 255, 0.4), 0 0 50px rgba(0, 229, 255, 0.1); }
}
/* 闪烁绿点 */
.live-dot {
width: 8px; height: 8px; border-radius: 50%;
background: #4ade80;
box-shadow: 0 0 8px rgba(74, 222, 128, 0.6);
animation: liveBlink .8s ease-in-out infinite;
}
@keyframes liveBlink { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .4; transform: scale(.7); } }
<button class="btn-calc" id="btnCalc" onclick="calc(true)">
<span class="btn-text"><span>▶</span> EXECUTE CALCULATION · 执行计算</span>
<span class="btn-live"><span class="live-dot"></span>REAL TIME CALCULATING 自动计算中...</span>
</button>
let realtimeTimer = null;
let isLive = false;
/* 实时计算触发器(防抖 300ms)*/
function triggerRealtime() {
if (realtimeTimer) clearTimeout(realtimeTimer);
realtimeTimer = setTimeout(() => {
calc(false); // false = 实时触发,不重置 live 状态
if (!isLive) {
isLive = true;
const btn = document.getElementById('btnCalc');
if (btn) btn.classList.add('live-mode');
}
}, 300);
}
/* calc 函数增加 manual 参数 */
function calc(manual = false){
try {
// manual=true 是手动点击,重置 live 状态
if (manual) {
if (realtimeTimer) { clearTimeout(realtimeTimer); realtimeTimer = null; }
isLive = false;
const btn = document.getElementById('btnCalc');
if (btn) btn.classList.remove('live-mode');
}
// ... 计算逻辑 ...
} catch(e) { /* ... */ }
}
所有 均需绑定 oninput="triggerRealtime()":
| 类型 | 示例 | 绑定方式 |
|---|---|---|
| ------ | ------ | ---------- |
| 无 oninput | | 添加 oninput="triggerRealtime()" |
| 有 oninput | | 追加 ; triggerRealtime() → oninput="syncFlow('vol'); triggerRealtime()" |
| 管件输入 | | 追加 ; triggerRealtime() → oninput="highlightFit(this); triggerRealtime()" |
所有按钮和状态文字均采用 英文(风格化)+ 中文 的双语格式:
| 状态 | 英文(风格化) | 中文 | 完整显示 |
|---|---|---|---|
| ------ | ------------------ | ------ | ---------- |
| 计算按钮(正常) | EXECUTE CALCULATION | 执行计算 | EXECUTE CALCULATION · 执行计算 |
| 计算按钮(实时中) | REAL TIME CALCULATING | 自动计算中... | REAL TIME CALCULATING 自动计算中... |
| Toast 成功 | — | — | ✅ 计算完成 |
| Toast 错误 | — | — | ⚠️ 计算出错: xxx |
原则:英文部分保留风格化(全大写、紧凑字母间距),中文部分紧随其后,用 · 或空格分隔。
> 适用场景:所有 Excel → 单文件 HTML 的转换任务。
> 目的:保持系列工具视觉风格统一,减少反复调整。
CSS :root 中的 --font-sans 必须优先声明微软雅黑:
:root {
--font-sans: 'Microsoft YaHei', '微软雅黑', 'Noto Sans SC', 'PingFang SC', system-ui, sans-serif;
}
SimSun、宋体 作为正文或标题字体label、按钮文字均使用 var(--font-sans)var(--font-mono)(JetBrains Mono / Fira Code)禁止使用文字按钮("亮色"/"暗色")。必须使用 太阳☀️ + 月亮🌙 SVG 图标 + 标签文字 的胶囊按钮。
.theme-toggle {
position: fixed;
top: 14px;
right: 14px;
z-index: 200;
display: flex;
align-items: center;
gap: 8px;
background: var(--bg-card);
border: 1px solid var(--bd);
border-radius: 100px;
padding: 5px 14px 5px 8px;
cursor: pointer;
transition: all 0.3s;
color: var(--txt-muted);
font-family: var(--font-sans);
font-size: 0.72rem;
letter-spacing: 0.06em;
box-shadow: var(--shadow);
}
.theme-toggle:hover {
border-color: var(--brand);
color: var(--brand);
box-shadow: 0 0 12px var(--brand-dim), var(--shadow);
}
[data-theme="dark"] .theme-toggle {
border-color: rgba(0,229,255,0.3);
box-shadow: 0 0 12px rgba(0,229,255,0.08), var(--shadow);
}
[data-theme="dark"] .theme-toggle:hover {
border-color: var(--brand);
box-shadow: 0 0 14px rgba(0,229,255,0.15), var(--shadow);
}
.theme-toggle .sun-icon,
.theme-toggle .moon-icon {
width: 16px; height: 16px;
transition: transform 0.3s, opacity 0.3s;
flex-shrink: 0;
}
.theme-toggle .sun-icon { display: block; }
.theme-toggle .moon-icon { display: none; }
[data-theme="dark"] .theme-toggle .sun-icon { display: none; }
[data-theme="dark"] .theme-toggle .moon-icon { display: block; }
.theme-toggle .tt-label { font-weight: 600; }
<button class="theme-toggle" id="themeToggle" title="切换亮/暗主题" aria-label="切换主题">
<svg class="sun-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
<svg class="moon-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
<span class="tt-label" id="ttLabel">DARK</span>
</button>
const themeToggle = document.getElementById('themeToggle');
const ttLabel = document.getElementById('ttLabel');
if (themeToggle) {
themeToggle.addEventListener('click', () => {
const html = document.documentElement;
const theme = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', theme);
if (ttLabel) ttLabel.textContent = theme === 'dark' ? 'LIGHT' : 'DARK';
});
}
禁止使用 sticky 顶部窄栏标题。必须使用居中大气版式,参考 储罐容积计算系统V1.1.html 的标题风格。
[固定右上角] .theme-toggle 按钮(见规范2)
[居中标题区] .page-header
├── .hd-badge 胶囊徽章(闪烁圆点 + 编号/标准)
├── h1 英文主标题 + 中文高亮
├── p.sub 副标题(计算方法说明)
└── .brand-bar 渐变分隔线
.page-header {
text-align: center;
padding: 36px 20px 28px;
animation: fadeDown 0.6s ease both;
}
.hd-badge {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--brand-dim);
border: 1px solid rgba(42,168,137,0.2);
border-radius: 100px;
padding: 5px 18px;
font-family: var(--font-mono);
font-size: 0.72rem;
letter-spacing: 1.5px;
color: var(--brand);
text-transform: uppercase;
margin-bottom: 18px;
transition: all 0.3s;
}
.hd-badge::before {
content: '';
width: 6px; height: 6px;
border-radius: 50%;
background: var(--brand);
animation: blink 2.2s ease infinite;
}
[data-theme="dark"] .hd-badge {
border-color: rgba(0,229,255,0.25);
box-shadow: 0 0 12px rgba(0,229,255,0.08);
}
[data-theme="dark"] .hd-badge::before {
background: var(--brand);
box-shadow: 0 0 8px var(--brand);
}
.page-header h1 {
font-size: clamp(1.2rem, 3vw, 1.9rem);
font-weight: 700;
letter-spacing: 2px;
color: var(--txt-primary);
margin-bottom: 10px;
}
.page-header h1 .accent-amb {
color: var(--amber);
font-weight: 500;
}
[data-theme="dark"] .page-header h1 .accent-amb { color: var(--brand); }
.page-header .sub {
font-size: 0.82rem;
color: var(--txt-sec);
letter-spacing: 0.5px;
}
.brand-bar {
width: 100px; height: 3px;
margin: 16px auto 0;
background: linear-gradient(90deg, var(--brand), var(--amber));
border-radius: 2px;
opacity: 0.7;
}
@keyframes fadeDown {
from { opacity: 0; transform: translateY(-12px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
<!-- 固定右上角主题切换按钮(规范2已定义) -->
<button class="theme-toggle" id="themeToggle" title="切换亮/暗主题">...</button>
<!-- 居中标题区 -->
<div class="page-header">
<div class="hd-badge">
[英文工具名] · VER X.X · [标准号] · [中文工具名]
</div>
<h1>[英文主标题] <span class="accent-amb">[中文副标题]</span></h1>
<p class="sub">[计算方法一] · [计算方法二] | [标准依据]</p>
<div class="brand-bar"></div>
</div>
.hd-badge 内容格式固定为:
[英文工具名] · VER X.X · [标准号] · [中文工具名]
VER X.X(全大写,空格,小数点分隔) · 分隔PIPE DIAMETER CALC · VER 1.1 · HG/T 20570.6-95 · 管径计算| # | 检查项 | 通过标准 |
|---|---|---|
| --- | -------- | ---------- |
| 1 | --font-sans 首选项是微软雅黑 | 无宋体/SimSun 出现 |
| 2 | 主题按钮是太阳/月亮图标 | 无"亮色"/"暗色"文字按钮 |
| 3 | 标题是居中大气版式 | 无 sticky 窄栏 |
| 4 | .hd-badge 含 VER X.X | 版本号存在且格式正确 |
| 5 | 暗色模式切换后标签文字变化 | 亮色→显示DARK;暗色→显示LIGHT |
严重程度:⭐⭐⭐⭐⭐(仅 Edge 复现,Chrome/Firefox 正常)
在 已复现环境:Microsoft Edge(Chromium 内核),Windows 11 Edge 对 关键发现:同一项目中「管道压力降」页面的 tip 表格(管壁粗糙度参考)在 Edge 中正常显示,其结构为极简模式。 核心策略:最大简化 table 结构,照搬已验证可用的参考文件 要点: 参考文件(已验证 Edge 正常): 化工计算工具常需引用工程标准中的曲线图/图表(非公式可表达),例如: 这些图的共同特点:无法用数学公式精确表示,用户需要"看图查值"。 结论:对于工程标准查图表,外部图片 + 同目录部署是最务实的方案。 在需要查图填值的输入框 label 上明确提示用户: SheetJS (xlsx.js)原生支持 Unicode/中文,无需特殊编码处理。直接传入中文字符串即可正确写入 注意: 结论:对于含大量中文的工程计算报告,window.print() 是最佳方案——利用浏览器原生渲染能力,中文完美支持,样式完全保留。 问题:通过 解决:在 HTML 内容字符串开头添加 UTF-8 BOM ( 原理: 解决:始终使用 共 1 个版本position: absolute 的 tooltip/tip 弹出框内使用 显示预设值参考表时:
根因分析
position: absolute 容器内复杂 结构存在渲染 bug:
因素 Edge 行为 ------ ----------- + 固定列宽 + !important忽略所有强制宽度设置 table-layout: fixed + !important 覆盖!important 与 Edge 渲染引擎冲突,规则失效thead / tbody 结构化标签触发 Edge 特殊渲染路径,列宽分配异常 默认左对齐 + 长内容 左侧溢出容器边界被 clip ✅ 最终解决方案(4 轮迭代验证)
❌ 第 1~2 轮:失败方案
方案1: table-layout:fixed + !important 强制列宽 → Edge 忽略 !important
方案2: 合并两列为单列(β和C用<br>换行) → 更糟,全白
✅ 第 3~4 轮:成功方案
<!-- ✅ 正确 HTML 结构 — 极简模式 -->
<div class="tip">
<div class="tip-head">流量系数 C 近似值参考表</div>
<table>
<tr><th>β (d₀/D)</th><th>C 参考值</th></tr>
<tr><td>0.20</td><td class="tv" onclick="fillC(0.60)">0.60</td></tr>
<!-- 更多行... -->
</table>
<div class="tip-foot">点击数值自动填入,或手动输入实测值</div>
</div>
/ +
/ / width: 100% + table-layout: fixed(让浏览器均分列宽)/* ✅ 正确 CSS */
.tip table {
width: 100%;
border-collapse: collapse;
table-layout: fixed; /* 关键:均分列宽 */
}
.tip th, .tip td {
padding: 6px 6px; /* 收紧内边距,留更多空间给内容 */
text-align: center; /* 关键:居中对齐防止截断 */
}
.tip .tv {
font-family: var(--font-mono);
font-weight: 600;
color: var(--brand);
cursor: pointer;
/* 不要设 text-align:right,统一 center */
}
.tip-foot {
text-align: center;
padding-left: 24px; /* 微调提示文字位置 */
}
经验总结清单
# 检查项 通过标准 --- -------- ---------- 1 无 已删除 2 无 / 已删除 3 有 table-layout: fixed两列均分宽度 4 单元格 text-align: center居中防截断 5 无任何 !important避免与 Edge 渲染冲突 6 padding 收紧至 ≤ 6px 为内容留空间 D:\RayClaw\PE Calculation\01-Pipelines\不可压缩流体压力降计算-Pipe Pressure Drop-V1.1.html 中「管壁粗糙度 ε ⬡ 参考」的 tip 表格坑点 6:工程标准关系图(C-Re-d₀/D 等)的处理策略
问题场景
推荐方案:外部图片 + Tab 页懒加载 + 全屏放大
方案架构
┌─────────────────────────────────────┐
│ [设计计算] [操作计算] [关系图 ★] │ ← Tab 切换
├─────────────────────────────────────┤
│ │
│ ┌───────────────────┐ │
│ │ │ │
│ │ 关系图 (jpg) │ │ ← <img> 加载外部图片
│ │ 点击可放大 │ │
│ │ │ │
│ └───────────────────┘ │
│ │
│ 📌 使用说明(步骤引导) │
│ 📊 来源:HG/T xxxxx-95 图 x.x.x │
│ [🔍 放大查看] │
└─────────────────────────────────────┘
HTML 实现
<!-- Tab 按钮 -->
<button class="tab-btn" id="btnTabChart" onclick="switchTab('chart')">📈 RELATION CHART · 关系图</button>
<!-- Tab 内容:关系图 -->
<div class="tab-content" id="tabChart">
<div class="card">
<div class="ctitle">
<span class="ico">📈</span>
<div class="ctitle-text">
<div class="ctitle-main">C-RE-D₀/D RELATION CHART</div>
<div class="ctitle-sub">限流孔板流量系数关系图 · HG/T 20570.15-95</div>
</div>
</div>
<!-- 图片区域 -->
<div style="text-align:center; padding:12px 0 20px;">
<img id="chartImg" src="" alt="C-Re-d0/D 关系图"
style="max-width:100%; height:auto; border-radius:var(--r-md);
box-shadow:var(--shadow-md); cursor:zoom-in;"
onclick="zoomChart()">
<div class="diagram-label" style="margin-top:14px;">
限流孔板 C-Re-d₀/D 关系图 · 流量系数 C 与雷诺数 Re 及 d₀/D 的关系
</div>
</div>
<!-- 使用说明 -->
<div style="margin-top:18px; padding:14px 18px;
background:var(--brand-lt);
border:1px solid rgba(42,168,137,0.15);
border-radius:var(--r-md); font-size:.82rem;
color:var(--txt-secondary); line-height:2;">
<div style="font-weight:700; color:var(--brand-dk); margin-bottom:8px;">📌 使用说明</div>
<div>① 根据设计计算得到的 <strong>d₀/D 比值</strong>,在图中找到对应曲线</div>
<div>② 根据雷诺数 <strong>Re</strong>,在曲线上查得对应的 <strong>C 值</strong></div>
<div>③ 将查得的 C 值填入输入框,重新计算</div>
<div>④ 计算后对比 C' 与 C,偏差 <strong>< 5%</strong> 为校核合格 ✓</div>
</div>
<!-- 来源标注 + 放大按钮 -->
<div style="margin-top:16px; display:flex; gap:10px; flex-wrap:wrap;">
<span class="info-tag">关系图来源:HG/T 20570.15-95 图 5.0.4</span>
<button class="btn-sm" onclick="zoomChart()" style="margin-left:auto;">🔍 放大查看</button>
</div>
</div>
</div>
JS — Tab 切换时懒加载图片(节省首屏加载)
function switchTab(tab) {
// ... tab 切换逻辑 ...
// 懒加载:仅在切换到 chart tab 时才加载图片
if (tab === 'chart') {
const chartImg = document.getElementById('chartImg');
if (!chartImg.src || chartImg.src === window.location.href) {
chartImg.src = '关系图.jpg'; // 图片文件与 HTML 同目录
}
}
}
JS — 全屏放大功能(点击图片触发)
function zoomChart() {
const img = document.getElementById('chartImg');
if (!img.src || img.src === window.location.href) {
toast('⚠️ 请先切换到「关系图」标签页查看图表', true);
return;
}
// 创建全屏遮罩层
const overlay = document.createElement('div');
overlay.id = 'chartOverlay';
overlay.style.cssText =
'position:fixed;inset:0;background:rgba(0,0,0,0.88);z-index:9999;' +
'display:flex;align-items:center;justify-content:center;' +
'cursor:zoom-out;animation:fadeIn .25s ease;';
overlay.onclick = () => { document.body.removeChild(overlay); };
// 克隆图片
const imgClone = document.createElement('img');
imgClone.src = img.src;
imgClone.style.cssText =
'max-width:95vw;max-height:95vh;object-fit:contain;' +
'border-radius:8px;box-shadow:0 8px 40px rgba(0,0,0,0.5);';
// 底部提示
const caption = document.createElement('div');
caption.textContent = '点击任意处关闭';
caption.style.cssText =
'position:absolute;bottom:20px;left:50%;transform:translateX(-50%);' +
'color:#fff;font-family:var(--font-mono);font-size:.75rem;' +
'letter-spacing:1px;opacity:.7;';
overlay.appendChild(imgClone);
overlay.appendChild(caption);
document.body.appendChild(overlay);
toast('🔍 放大查看 — 点击任意处关闭');
}
// 补充 fadeIn 动画定义(CSS 中)
// @keyframes fadeIn { from{opacity:0} to{opacity:1} }
为什么不用 SVG/Canvas 重绘?
方案 优点 缺点 ------ ------ ------ 外部图片 (推荐) ✅ 保真度高、实现简单、懒加载快 文件体积稍大 SVG 矢量重绘 无限缩放、体积小 工程曲线难以精确还原、开发量大 Canvas 绘制 可交互 同上 + 性能开销 Base64 内嵌 单文件无依赖 HTML 体积暴增(图片转 base64 约 ×4/3) 输入框联动提示
<label>校核流量系数 C' (查限流孔板 C-Re-d0/D 关系图后填入)</label>
<input type="number" id="d_Cp" value="0.6" ...>
坑点 7:导出功能(Excel / PDF)及中文乱码问题
Excel 导出 — SheetJS (xlsx.js)
CDN 引入
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
导出函数模板
function exportDesignXLSX() {
try {
if (typeof XLSX === 'undefined') {
toast('⚠️ 正在加载 Excel 库,请稍候重试', true);
return;
}
// 1. 收集数据
const qv = document.getElementById('d_qv').value;
const rho = document.getElementById('d_rho').value;
// ... 更多字段 ...
// 2. 构建工作簿
const wb = XLSX.utils.book_new();
const data = [
['限流孔板设计计算 / Orifice Design Calculation'],
['HG/T 20570.15-95', '生成日期:' + new Date().toLocaleDateString('zh-CN')],
[],
['参数名', '数值', '单位', '备注'],
['体积流量 qv', parseFloat(qv), 'm³/h', ''],
// ... 更多行 ...
];
// 3. 创建工作表并设置列宽
const ws = XLSX.utils.aoa_to_sheet(data);
ws['!cols'] = [{wch:22}, {wch:14}, {wch:12}, {wch:30}];
// 4. 导出下载
XLSX.utils.book_append_sheet(wb, ws, '设计计算');
XLSX.writeFile(wb, '限流孔板设计计算_' + new Date().toISOString().slice(0,10) + '.xlsx');
toast('✅ Excel 已导出');
} catch(e) {
toast('⚠️ Excel 导出失败: ' + e.message, true);
}
}
中文处理
.xlsx 文件。.xlsx 格式基于 XML(内部 UTF-8),不存在 GBK 乱码问题。只有旧版 .xls (BIFF格式) 才可能遇到编码问题。PDF 导出 — window.print() 方案(推荐)
为什么不用 jsPDF?
方案 中文支持 表格支持 样式还原 复杂度 ------ ---------- ---------- ---------- -------- window.print() ✅ 完美(浏览器原生) 完整 CSS 表格 100% 还原 低 jsPDF + autoTable 需额外字体文件 基础表格 有限 高 html2canvas + jsPDF 完美但模糊 截图方式 像素级但大文件 中 PDF 导出函数模板
function exportDesignPDF() {
try {
// 1. 收集数据(同 Excel)
const qv = document.getElementById('d_qv').value;
// ...
// 2. 构建 HTML 内容(用于打印)
const today = new Date().toLocaleDateString('zh-CN');
const htmlContent = `\uFEFF<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>限流孔板设计计算报告</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
font-family: 'Microsoft YaHei', '微软雅黑', SimSun, Arial, sans-serif;
font-size: 12pt; color: #1a1a1a;
padding: 30px 40px; background: #fff;
}
/* 报告标题区 */
.header {
text-align: center; margin-bottom: 30px;
border-bottom: 2px solid #2aa889; padding-bottom: 16px;
}
.header h1 { font-size: 18pt; color: #2aa889; margin-bottom: 6px; }
.header p { font-size: 10pt; color: #666; }
/* 数据表格 */
table { width: 100%; border-collapse: collapse; margin: 16px 0; }
th {
background: #2aa889; color: #fff; padding: 8px 12px;
font-size: 10pt; text-align: left;
}
td {
padding: 7px 12px; border: 1px solid #ddd; font-size: 10pt;
}
tr:nth-child(even) td { background: #f8f9fa; }
/* 页脚信息 */
.footer {
margin-top: 24px; padding-top: 12px;
border-top: 1px solid #ddd; font-size: 9pt; color: #999;
}
/* 打印优化 */
@media print {
body { padding: 20px; }
.no-print { display: none !important; }
page { margin: 15mm; }
}
</style>
</head>
<body>
<div class="header">
<h1>限流孔板设计计算报告</h1>
<p>Orifice Plate Design Calculation Report · HG/T 20570.15-95</p>
<p style="font-size:9pt;color:#999;margin-top:4px;">生成日期:${today}</p>
</div>
<table>
<thead>
<tr><th>参数名</th><th>数值</th><th>单位</th><th>备注</th></tr>
</thead>
<tbody>
<tr><td>体积流量 qv</td><td>${parseFloat(qv)}</td><td>m³/h</td><td></td></tr>
<!-- 更多行... -->
</tbody>
</table>
<div class="footer">
<p>本报告由限流孔板计算工具自动生成 · 依据 HG/T 20570.15-95</p>
</div>
</body>
</html>`;
// 3. 打开新窗口并写入 HTML
const printWindow = window.open('', '_blank');
printWindow.document.write(htmlContent);
printWindow.document.close();
// 4. 触发打印对话框
setTimeout(() => { printWindow.print(); }, 500);
toast('📄 PDF 导出:请在打印对话框选择"另存为 PDF"');
} catch(e) {
toast('⚠️ PDF 导出失败', true);
}
}
🔑 中文乱码问题的完整解决方案
场景 1:PDF 导出中文乱码 → ✅
\uFEFF BOMwindow.open() 写入 HTML 字符串时,部分浏览器/打印场景下中文显示为乱码(□□ 或 ???)\uFEFF)// ✅ 正确:BOM 让浏览器识别为 UTF-8 编码
const htmlContent = `\uFEFF<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
...
\uFEFF 是 UTF-8 BOM(Byte Order Mark),写在文件/流的最开头,告诉解析器"这是 UTF-8 编码"。没有 BOM 时,某些 Windows 浏览器的打印引擎可能按系统默认编码(GBK/GB2312)解析,导致中文乱码。场景 2:Excel 导出中文乱码 → ✅ 使用 .xlsx 格式
格式 编码 中文支持 ------ ------ ---------- .xlsx (Office Open XML) ✅内部 UTF-8 XML 完美支持 .xls (BIFF5/8)二进制格式 可能乱码 CSV 取决于编辑器编码 极易乱码 XLSX.writeFile() 生成 .xlsx 格式(默认行为),不要指定 .xls 或 .csv。场景 3:HTML 文件本身打开乱码 → ✅
<!-- 主 HTML 文件头部必须包含 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"> <!-- 必须! -->
...
中文编码速查表
场景 解决方案 代码位置 ------ ---------- ---------- PDF 导出 window.print() 内容前加 \uFEFF BOMJS 字符串模板开头 Excel 导出 SheetJS 使用 .xlsx 格式(默认)XLSX.writeFile(wb, 'name.xlsx')HTML 主文件 第一行外部 CSS/JS 文件 UTF-8 with BOM 保存 文件保存时选择编码 @font-face 中文字体 Google Fonts Noto Sans SC 引入UI 导出按钮布局
<div class="exl">EXPORT · 导出</div>
<div style="display:flex; gap:8px; flex-wrap:wrap;">
<button class="btn-sm xl" onclick="exportDesignXLSX()">📊 Excel</button>
<button class="btn-sm" onclick="exportDesignPDF()">📄 PDF</button>
</div>
快速命令
# 查看 XLSM 内部结构
python -c "import zipfile; z=zipfile.ZipFile('file.xlsm'); print(z.namelist())"
版本历史
🔗 相关推荐
Word / DOCX
HTML转微信小程序