USEGEAR

导航

学习unigui【41】htmlframe的交互现象

场景:

1、mainform中有许多控件,其中有htmlframe。那就有回调等等交互。
2、htmlframe中显示相关指标。要初始化和定时刷新数据

问题:死活和htmlframe交互不到。why?我不懂web原理。

有老师精通的,请指导我。这个如果是坑,那么死在这里的的人不会少。所以大家去学vue等等了。

代码仅仅记录该坑爹的过程:

1、htmlframe中有代码(我也不懂js)

  1 <script>
  2 
  3     // ===== 初始化占位符(Delphi 替换这里)=====
  4   // 替换成:{"header":{...},"metricsTitle":"...","metrics":[...]}
  5   const __INIT_WATER_DATA__ = {[&WATER_INIT_JSON]};
  6 
  7 
  8   // ===== 渲染 =====
  9   function fmt(v){
 10     if (v === null || v === undefined) return '--';
 11     if (typeof v === 'number') return (Math.round(v*100)/100).toString();
 12     return String(v);
 13   }
 14 
 15   function renderMetrics(list){
 16     const grid = document.getElementById('grid');
 17     grid.innerHTML = '';
 18     (list || []).forEach(m => {
 19       const el = document.createElement('div');
 20       el.className = 'mCard';
 21       el.innerHTML = `
 22         <div class="mHead">
 23           <div class="mName">${m.name ?? ''}</div>
 24           <div class="mUnit">${m.unit ? '('+m.unit+')' : ''}</div>
 25         </div>
 26         <div class="mVals">
 27           <div class="mVal"><div class="mNum">${fmt(m.in)}</div><div class="mCap">进水</div></div>
 28           <div class="mVal"><div class="mNum">${fmt(m.out)}</div><div class="mCap">出水</div></div>
 29         </div>`;
 30       grid.appendChild(el);
 31     });
 32   }
 33 
 34   // ===== 顶部提示条 =====
 35   const hint = document.getElementById('refreshHint');
 36   function showHint(text, loading){
 37     if (!hint) return;
 38     hint.textContent = text;
 39     hint.style.display = 'flex';
 40     if (loading) hint.classList.add('loading'); else hint.classList.remove('loading');
 41   }
 42   function hideHint(){
 43     if (!hint) return;
 44     hint.classList.remove('loading');
 45     hint.style.display = 'none';
 46   }
 47 
 48   // ===== 统一入口:收到数据就更新并停转圈 =====
 49   window.setDashboardData = function(data){
 50     data = data || {};
 51     document.getElementById('heroTitle').textContent = (data.header && data.header.title) || '开发区第三污水处理厂';
 52     document.getElementById('metricsTitle').textContent = data.metricsTitle || '水质监测';
 53     renderMetrics(data.metrics || []);
 54     hideHint();  
 55     if (window.__waterRefreshDone) window.__waterRefreshDone(); // ★关键:刷新完成解锁  
 56   };
 57   
 58   // ★立即跑一次:不依赖 load(UnimHTMLFrame 动态注入时经常不触发 load)
 59   try { window.setDashboardData(__INIT_WATER_DATA__); } catch(e) {}
 60 
 61   // ===== 收父页面回推 =====
 62   window.addEventListener('message', function(e){
 63     const d = e.data;
 64     if (!d || !d.type) return;
 65     if (d.type === 'dashboardData'){
 66         // ★如果父页面推空数据,就别覆盖初始化界面
 67     if (!d.payload || !d.payload.metrics || d.payload.metrics.length === 0) return;
 68       window.setDashboardData(d.payload);
 69     }
 70   });
 71 
 72   // ===== 下拉刷新:只发消息给父页面 =====
 73 // ===== 下拉刷新(时间门禁·不抢滚动版)=====
 74 // 规则:必须从“顶部热区”开始下拉;达到阈值后必须“停住不动”HOLD_MS 才触发。
 75 // 全程不 preventDefault,MainForm 滚动不会被 iframe 抢走。
 76 (function(){
 77   const PULL_PX  = 35;   // 比你原来 25 更不敏感
 78   const HOLD_MS  = 350;  // 比你原来 450 更稳
 79   const EDGE_PX  = 70;   // 只有从屏幕顶部 70px 内开始的手势才算“刷新手势”
 80   const MAX_DY   = 220;  // 防止夸张拉动造成误判
 81 
 82   let state = 'idle';       // idle | tracking | armed | refreshing
 83   let startY = 0;
 84   let dy = 0;
 85   let holdTimer = null;
 86 
 87   function clearHold(){
 88     if (holdTimer){
 89       clearTimeout(holdTimer);
 90       holdTimer = null;
 91     }
 92   }
 93 
 94   function doRefresh(){
 95     if (state === 'refreshing') return;
 96     state = 'refreshing';
 97     clearHold();
 98     showHint('正在更新水质数据…', true);
 99     window.parent.postMessage({type:'refreshWaterData'}, '*');
100   }
101 
102   // 只允许“从顶部热区”开始上膛(否则一律放行给 MainForm 滚动)
103   document.addEventListener('touchstart', function(e){
104     if (state === 'refreshing') return;
105 
106     const y0 = e.touches[0].clientY;
107 
108     // ★关键:不是从顶部热区开始的手势,直接忽略(不进入 tracking)
109     if (y0 > EDGE_PX){
110       state = 'idle';
111       clearHold();
112       return;
113     }
114 
115     startY = y0;
116     dy = 0;
117     state = 'tracking';
118     clearHold();
119   }, {passive:true});
120 
121   document.addEventListener('touchmove', function(e){
122     if (state !== 'tracking' && state !== 'armed') return;
123     if (state === 'refreshing') return;
124 
125     const y = e.touches[0].clientY;
126     dy = y - startY;
127 
128     // 只认向下;向上/回弹就取消
129     if (dy <= 0){
130       hideHint();
131       state = 'tracking';
132       clearHold();
133       return;
134     }
135 
136     // 限幅,避免夸张拖动误判
137     if (dy > MAX_DY) dy = MAX_DY;
138 
139     // ★注意:这里绝对不 preventDefault,避免抢 MainForm 滚动
140 
141     // 小于阈值:只提示,不计时
142     if (dy < PULL_PX){
143       showHint('下拉继续保持可刷新…', false);
144       state = 'tracking';
145       clearHold();
146       return;
147     }
148 
149     // >=阈值:提示“保持”,并且——关键:每次 move 都重置 timer
150     showHint('保持不动即可刷新…', false);
151     state = 'armed';
152 
153     clearHold();
154     holdTimer = setTimeout(function(){
155       // 只有用户真的“停住不动”了 HOLD_MS 才触发
156       if (state === 'armed' && dy >= PULL_PX){
157         doRefresh();
158       }
159     }, HOLD_MS);
160 
161   }, {passive:true}); // passive:true:不阻止滚动,完全放行给外层
162 
163   document.addEventListener('touchend', function(){
164     if (state === 'armed' || state === 'tracking'){
165       hideHint();
166       clearHold();
167       state = 'idle';
168     }
169     // refreshing:等数据回来解锁
170   }, {passive:true});
171 
172   document.addEventListener('touchcancel', function(){
173     if (state !== 'refreshing'){
174       hideHint();
175       clearHold();
176       state = 'idle';
177     }
178   }, {passive:true});
179 
180   // 刷新完成解锁(你 setDashboardData 里已经调用了它)
181   window.__waterRefreshDone = function(){
182     clearHold();
183     hideHint();
184     state = 'idle';
185   };
186 })();
187 
188 
189 
190 // ====== waterIframeReady:握手重试(不依赖 load,立即执行)======
191 (function(){
192   if (window.__waterReadyInited) return;   // 防重复
193   window.__waterReadyInited = true;
194 
195   var ok = false;
196   var t = null;
197 
198   function onMsg(e){
199     var d = e.data;
200     if (d && d.type === 'waterIframeAck'){
201       ok = true;
202       try { if (t) clearInterval(t); } catch(ex){}
203     }
204   }
205   window.addEventListener('message', onMsg);
206 
207   function ping(){
208     try { window.parent.postMessage({type:'waterIframeReady'}, '*'); } catch(ex){}
209   }
210 
211   // 立即发一次 + 重试
212   ping();
213   t = setInterval(function(){
214     if (ok){ try{ clearInterval(t); }catch(ex){}; return; }
215     ping();
216   }, 300);
217 
218   // 10 秒兜底停止
219   setTimeout(function(){
220     try { if (t) clearInterval(t); } catch(ex){}
221   }, 10000);
222 })();
223 
224 
225 
226 
227 
228 
229   
230   
231 window.addEventListener('load', function(){
232   // 让提示条先出现,避免“空白”
233   if (typeof showHint === 'function') showHint('正在加载水质数据…', true);
234 
235   // ★首次进入先用“初始化 JSON”渲染一遍
236   try { window.setDashboardData(__INIT_WATER_DATA__); } catch(e){}
237 
238 
239 });
240 
241 
242 
243 
244 </script>
View Code

2、在mainform要初始化htmlframe中显示的数据:我很蠢,刚开始没有办法,就采用使用占位符初始显示数据(理解之后就多此一举了)
3、接下来需要在移动设备中定时刷新数据。就这简单。死活搞不定。
关键片段1:

procedure TMainmForm.UnimFormAfterShow(Sender: TObject);
begin
  UniSession.AddJS(
    'if(!window.__waterBridge){' +
    '  window.__waterBridge=true;' +
    '  window.__waterFrameWin=null;' +
    '  window.addEventListener("message", function(e){' +
    '    var d=e.data;' +
    '    if(!d || !d.type) return;' +
    // ✅ ① iframe ready:记住 e.source(iframe window)+ 回 ACK + 触发首次刷新
    '    if(d.type==="waterIframeReady"){' +
    '      window.__waterFrameWin = e.source;' +
    '      try{ window.__waterFrameWin.postMessage({type:"waterIframeAck"}, "*"); }catch(ex){}' +
    '      var frm = (window.UniAppManager && UniAppManager.getActiveForm) ? UniAppManager.getActiveForm() : null;' +
    '      if(frm){ ajaxRequest(frm, "refreshWaterData", []); }' +
    '      return;' +
    '    }' +
    // ✅ ② 下拉触发刷新:同样记住 e.source,再刷新
    '    if(d.type==="refreshWaterData"){' +
    '      window.__waterFrameWin = e.source;' +
    '      var frm2 = (window.UniAppManager && UniAppManager.getActiveForm) ? UniAppManager.getActiveForm() : null;' +
    '      if(frm2){ ajaxRequest(frm2, "refreshWaterData", []); }' +
    '      return;' +
    '    }' +
    '  });' +
    '}'
  );
end;
View Code

关键片段2:

procedure TMainmForm.UnimHTMLFrame1AjaxEvent(Sender: TComponent;
  EventName: string; Params: TUniStrings);
var
  jsonStr: string;
begin
  UniSession.Log('调用UnimFormAjaxEvent  EventName=' + EventName);

  if EventName = 'refreshWaterData' then
  begin
    var q : TFDQuery;
    q := TFDQuery.Create(nil);
    q.Connection := MainModule.UniMainModule.FDConnection1;
    try
      q.Close;
      q.SQL.Text := 'select user001.fn_water_dashboard_init_json()::text as jsonStr'; // 例:刷新函数
      q.Open;
      jsonStr := q.FieldByName('jsonStr').AsString;
    finally
      q.Free;
    end;

  UniSession.AddJS( 'if(window.__waterFrameWin){' +
   ' window.__waterFrameWin.postMessage({type:"dashboardData", payload:' + jsonStr +
    '}, "*");' + '}' );
  end;

end;
View Code

关键片段3:

procedure TMainmForm.unmtmr1Timer(Sender: TObject);
begin
  UniSession.AddJS(
    '(function(){' +
    '  try{' +
    '    if(!window.__waterFrameWin) return;' +
    '    if(typeof ' + UnimHTMLFrame1.JSName + ' === "undefined") { console.log("frame js not ready"); return; }' +
    '    ajaxRequest(' + UnimHTMLFrame1.JSName + ', "refreshWaterData", []);' +
    '  }catch(e){ console.log("timer refresh err", e); }' +
    '})();'
  );
end;
View Code

下面是ai给出的总结:
我来理解:mainform--htmlFrame之间要搭桥。我们在通过maimform操作,就能到达htmlFrame,是不是?

对,你理解到点子上了。

在 UniGUI 里你可以把它想成两层世界:

  • MainForm(UniGUI/ExtJS 世界):按钮、工具条、定时器、ajaxRequest 都在这里。

  • UnimHTMLFrame(WebView/iframe 世界):你那段 HTML/JS 在这里跑,window.setDashboardData() 也在这里。

这两层默认互相“看不见”,所以必须“搭桥”。


这座桥到底是什么?

桥的核心就是:MainForm 拿到 HTMLFrame 对应的 window 引用,也就是你代码里的:

window.__waterFrameWin = e.source;

这句的意义是:

“以后 MainForm 想给 HTMLFrame 发消息,就用这个 window 去 postMessage。”

没有它,你 MainForm 就只能在外面干瞪眼:发不进去、推不进 iframe。


桥怎么搭起来?

必须让 HTMLFrame 主动喊一声“我在这”:

HTML 里:

 
window.parent.postMessage({type:'waterIframeReady'}, '*');

MainForm 里监听到后:

  • 记住 e.source(这就是 iframe 的 window)

  • 回一个 ACK(可选但强烈建议)

  • 立刻触发一次 ajaxRequest(..., "refreshWaterData")

这一步之后,桥就通了。


搭桥后 MainForm 能做什么?

MainForm 可以“到达” HTMLFrame:

  • 定时器:自动拉数据 → postMessage 推给 HTMLFrame → HTMLFrame 更新 UI

  • 按钮:手动刷新 → 同样走一条链路

  • HTMLFrame 下拉:iframe 发 refreshWaterData → MainForm 收到 → 拉数据 → 回推

这就是你看到的现象:
“下拉一次后,按钮/定时器才工作”
因为下拉那次刚好把桥(__waterFrameWin)建立了。


最关键的一句话

你不是“通过 MainForm 直接操作 HTMLFrame DOM”,而是:
MainForm 通过桥(保存的 iframe window)给 HTMLFrame 发消息,让 HTMLFrame 自己更新。

这才是 UniGUI 里最稳、最干净的玩法。

接下来你只要记住一个原则:任何能触发刷新数据的动作,都必须保证桥已建立__waterFrameWin != null),就不会再出现“某些情况下能刷,某些情况下不能刷”的鬼故事了。

posted on 2026-01-08 19:02  USEGEAR  阅读(4)  评论(0)    收藏  举报