Commit a3677b9e by James Yuan

feat: 纳入引擎触控资源,并修复电表日耗毛刺与大屏 touch-action

将 dte.js/gis.html/gis.js 纳入仓库便于部署;拒绝表计通讯毛刺导致的今日用电暴涨;大屏 iframe/舞台禁用浏览器默认触控手势。

Co-authored-by: Cursor <cursoragent@cursor.com>
parent a75df7d5
......@@ -209,11 +209,9 @@ export async function recordLabCoolingDaily(readings: LabCoolingMeterReading[],
ON CONFLICT (usage_date, meter_key) DO UPDATE SET
meter_name = EXCLUDED.meter_name,
device_id = EXCLUDED.device_id,
end_kwh = GREATEST(EXCLUDED.end_kwh, energy_daily_meter.end_kwh),
usage_kwh = GREATEST(
0,
GREATEST(EXCLUDED.end_kwh, energy_daily_meter.end_kwh) - energy_daily_meter.start_kwh
),
start_kwh = LEAST(energy_daily_meter.start_kwh, EXCLUDED.start_kwh),
end_kwh = EXCLUDED.end_kwh,
usage_kwh = GREATEST(0, EXCLUDED.end_kwh - LEAST(energy_daily_meter.start_kwh, EXCLUDED.start_kwh)),
updated_at = NOW()`,
[date, r.key, r.name, r.deviceId || null, row.start, row.end, row.usage],
)
......
/**
* 日差量公共逻辑:累积表计读数日内只进不退,避免通讯毛刺/仿真抖动导致「今日 kWh」跳变。
* 日差量公共逻辑:累积表计读数日内只进不退,但拒绝通讯毛刺(上千 kWh 瞬时跳变)。
*/
export type DailyMeterRow = { start: number; end: number; usage: number }
const EPS = 1e-6
/**
* 单次轮询相对上次 end 允许的最大跃升/回落 (kWh)。
* 实验室级负载即便 100 kW 连续,1 分钟也才约 1.7 kWh;
* 实测 E/PAU 表「用电量」常年稳定,偶发 4xxx~5xxx 毛刺。
*/
export const MAX_READING_STEP_KWH = 30
export function memDayKey(date: string, meterKey: string) {
return `${date}|${meterKey}`
}
......@@ -21,7 +28,8 @@ export function normalizeDailyRow(start: number, end: number, usage?: number): D
/**
* 用最新累积读数刷新当日差量。
* - 新行:优先用昨末日值作 start;若当前读数已低于昨日 end,视为换表/错位,按当前起算
* - 已有行:读数回退则忽略(保持原 end/usage)
* - 已有行:小幅回退忽略;大幅回退视为收回毛刺,回写 end
* - 单次跃升过大:视为毛刺,不抬高 end
*/
export function applyMonotonicReading(
mem: Map<string, DailyMeterRow>,
......@@ -34,21 +42,43 @@ export function applyMonotonicReading(
const prev = mem.get(yestKey)
let start = prev && Number.isFinite(prev.end) ? prev.end : reading
if (reading + EPS < start) start = reading
// 日切后首包就比基线暴涨 → 毛刺,当日先按「无增量」挂起,等稳定读数
if (reading - start > MAX_READING_STEP_KWH) {
row = normalizeDailyRow(start, start, 0)
mem.set(dateKey, row)
return 0
}
row = normalizeDailyRow(start, reading)
mem.set(dateKey, row)
return row.usage
}
// 读数已低于当日起点:基线被抬高或表计复位 → 重新锚定,避免长期 0 后突然暴涨
// 读数已低于当日起点:基线被抬高或表计复位 → 重新锚定
if (reading + EPS < row.start) {
row.start = reading
row.end = reading
row.usage = 0
return 0
}
// 日内累积只进不退:过滤通讯毛刺 / 仿真抖动回退
const drop = row.end - reading
if (reading + EPS < row.end) {
// 大幅回落:收回此前误锁的毛刺 end
if (drop > MAX_READING_STEP_KWH) {
row.end = reading
row.usage = Math.max(0, reading - row.start)
return row.usage
}
// 小幅通讯抖动回退:保持原 end/usage
return row.usage
}
const rise = reading - row.end
// 单次暴涨:毛刺,不推进
if (rise > MAX_READING_STEP_KWH) {
return row.usage
}
row.end = reading
row.usage = Math.max(0, reading - row.start)
return row.usage
......
......@@ -227,11 +227,9 @@ export async function recordLabPowerDaily(readings: LabPowerMeterReading[], at =
ON CONFLICT (usage_date, meter_key) DO UPDATE SET
meter_name = EXCLUDED.meter_name,
device_id = EXCLUDED.device_id,
end_kwh = GREATEST(EXCLUDED.end_kwh, energy_daily_meter.end_kwh),
usage_kwh = GREATEST(
0,
GREATEST(EXCLUDED.end_kwh, energy_daily_meter.end_kwh) - energy_daily_meter.start_kwh
),
start_kwh = LEAST(energy_daily_meter.start_kwh, EXCLUDED.start_kwh),
end_kwh = EXCLUDED.end_kwh,
usage_kwh = GREATEST(0, EXCLUDED.end_kwh - LEAST(energy_daily_meter.start_kwh, EXCLUDED.start_kwh)),
updated_at = NOW()`,
[date, r.key, r.name, r.deviceId || null, row.start, row.end, row.usage],
)
......
......@@ -1204,6 +1204,9 @@ onBeforeUnmount(() => {
.scene, .ue-frame {
position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
background: #000;
/* 避免父页吃掉双指 pinch,事件才能进 iframe 内 dte.js */
touch-action: none;
-ms-touch-action: none;
}
.vp-topbar, .vp-tip {
position: absolute; z-index: 5;
......
......@@ -14,6 +14,9 @@
font-family: var(--body-font);
/* 左右业务栏宽度(固定 350px) */
--screen-side-w: 350px;
touch-action: none;
-ms-touch-action: none;
overscroll-behavior: none;
}
.stage::before {
content: none;
......
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- 空 favicon,避免浏览器标签/地址栏出现珊瑚色小圆点 -->
<link rel="icon" href="data:," />
<style>
:root {
--gray: #9e9e9e;
--theme-color: #ff4400;
--cue: black;
color-scheme: dark;
}
html::-webkit-scrollbar,
textarea::-webkit-scrollbar {
display: none;
}
html, body {
touch-action: none;
-ms-touch-action: none;
overscroll-behavior: none;
}
body {
display: flex;
height: 100vh;
width: 100vw;
margin: 0;
background-color: black;
}
main {
/* overflow-y: scroll; */
padding: 0rem;
flex-grow: 1;
touch-action: none;
/* color: white; */
}
video {
height: 100%;
width: 100%;
display: block;
touch-action: none;
-ms-touch-action: none;
user-select: none;
-webkit-user-select: none;
}
video::cue {
font-size: medium;
/* line-height: 2 !important; */
background-color: transparent;
text-shadow: black 0 0 0px;
color: var(--cue);
}
.queue {
width: 50rem;
height: 12rem;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
left: calc(50% - 25rem);
top: calc(50% - 6rem);
}
.queue > span {
font-size: 3rem;
font-style: italic;
text-align: center;
color: #333;
text-shadow:
1px 1px 1px #aaa,
2px 2px 1px #999,
3px 3px 1px #888,
4px 4px 1px #777;
-webkit-background-clip: text;
background-clip: text;
}
.queue > span:after {
content: '';
animation: dots 2s infinite;
}
@keyframes dots {
0% {
content: '.';
/* animation-timing-function: steps(1, end); */
}
25% {
content: '..';
/* animation-timing-function: steps(2, end); */
}
50% {
content: '...';
/* animation-timing-function: steps(3, end); */
}
75% {
content: '....';
/* animation-timing-function: steps(4, end); */
}
100% {
content: '.';
}
}
</style>
</head>
<body>
<main>
<video is="peer-stream" onplaying="getStats()">
<track default kind="captions" srclang="en" />
</video>
<div class="queue" id="loading" style="display: none;">
<span>Queueing, please wait<br></span>
</div>
</main>
<script type="text/javascript" src="gis.js"></script>
<script type="text/javascript" src="scene-embed.js"></script>
<!-- <script type="text/javascript" src="demo.js"></script> -->
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment