@steedos/service-ui 3.0.15-beta.2 → 3.0.15-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Steedos Mobile Bridge - Capacitor 原生能力桥接
|
|
3
|
+
*
|
|
4
|
+
* 此脚本作为 Steedos client.js 自动加载到 H5 页面中,
|
|
5
|
+
* 检测 Capacitor 原生环境并提供推送注册、通知跳转、文件上传/下载等能力。
|
|
6
|
+
* 项目方只需安装此 Steedos package 即可获得移动端能力。
|
|
7
|
+
*/
|
|
8
|
+
(function () {
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
function boot() {
|
|
12
|
+
var Capacitor = window.Capacitor;
|
|
13
|
+
if (!Capacitor || !Capacitor.isNativePlatform || !Capacitor.isNativePlatform()) {
|
|
14
|
+
// Android WebView 中 native-bridge.js 由 MainActivity 注入,可能还没加载完
|
|
15
|
+
// androidBridge 是原生端注入的 JS interface,存在说明在 Android WebView 中
|
|
16
|
+
if (window.androidBridge && !window.__mobileBridgeRetryCount) {
|
|
17
|
+
window.__mobileBridgeRetryCount = 0;
|
|
18
|
+
}
|
|
19
|
+
if (window.androidBridge && window.__mobileBridgeRetryCount < 10) {
|
|
20
|
+
window.__mobileBridgeRetryCount++;
|
|
21
|
+
console.log('[MobileBridge] Waiting for Capacitor bridge injection, retry ' + window.__mobileBridgeRetryCount);
|
|
22
|
+
setTimeout(boot, 200);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
console.log('[MobileBridge] Not in Capacitor native environment, skipping.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var Plugins = Capacitor.Plugins || {};
|
|
30
|
+
console.log('[MobileBridge] Capacitor native detected, platform:', Capacitor.getPlatform());
|
|
31
|
+
|
|
32
|
+
// ============ 推送通知 ============
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 上报推送设备 token 到 Steedos 服务端
|
|
36
|
+
* 调用 Moleculer action: push-notifications.register
|
|
37
|
+
*/
|
|
38
|
+
function registerPushDevice(token) {
|
|
39
|
+
var platform = Capacitor.getPlatform(); // 'ios' | 'android'
|
|
40
|
+
var provider = 'fcm'; // @capacitor/push-notifications 使用 FCM (Android) / APNs via FCM (iOS)
|
|
41
|
+
|
|
42
|
+
fetch(window.location.origin + '/service/api/push-notifications/register', {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
credentials: 'include',
|
|
46
|
+
body: JSON.stringify({ device_id: token, platform: platform, provider: provider })
|
|
47
|
+
}).then(function (res) { return res.json(); })
|
|
48
|
+
.then(function (data) {
|
|
49
|
+
if (data && data.error) {
|
|
50
|
+
console.warn('[MobileBridge] Device register error:', data.error);
|
|
51
|
+
} else {
|
|
52
|
+
console.log('[MobileBridge] Push device registered:', data._id);
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
.catch(function (e) {
|
|
56
|
+
console.error('[MobileBridge] Push device register failed:', e);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 注销推送设备(登出时调用)
|
|
62
|
+
* 调用 Moleculer action: push-notifications.unregister
|
|
63
|
+
*/
|
|
64
|
+
function unregisterPushDevice() {
|
|
65
|
+
var token = localStorage.getItem('push_token');
|
|
66
|
+
if (!token) return;
|
|
67
|
+
|
|
68
|
+
fetch(window.location.origin + '/service/api/push-notifications/unregister', {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
credentials: 'include',
|
|
72
|
+
body: JSON.stringify({ device_id: token })
|
|
73
|
+
}).then(function (res) { return res.json(); })
|
|
74
|
+
.then(function (data) {
|
|
75
|
+
if (data && data.error) {
|
|
76
|
+
console.warn('[MobileBridge] Device unregister error:', data.error);
|
|
77
|
+
} else {
|
|
78
|
+
console.log('[MobileBridge] Push device unregistered');
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
.catch(function (e) { console.error('[MobileBridge] Unregister error:', e); });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function initPushNotifications() {
|
|
85
|
+
var PushNotifications = Plugins.PushNotifications;
|
|
86
|
+
if (!PushNotifications) {
|
|
87
|
+
console.warn('[MobileBridge] PushNotifications plugin not available');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
PushNotifications.requestPermissions().then(function (permission) {
|
|
92
|
+
if (permission.receive !== 'granted') {
|
|
93
|
+
console.warn('[MobileBridge] Push permission denied');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
PushNotifications.register();
|
|
97
|
+
}).catch(function (e) {
|
|
98
|
+
console.error('[MobileBridge] Push permission error:', e);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
PushNotifications.addListener('registration', function (token) {
|
|
102
|
+
console.log('[MobileBridge] Push token:', token.value);
|
|
103
|
+
localStorage.setItem('push_token', token.value);
|
|
104
|
+
registerPushDevice(token.value);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
PushNotifications.addListener('registrationError', function (error) {
|
|
108
|
+
console.error('[MobileBridge] Push registration error:', error);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// 前台收到通知
|
|
112
|
+
PushNotifications.addListener('pushNotificationReceived', function (notification) {
|
|
113
|
+
console.log('[MobileBridge] Foreground notification:', notification);
|
|
114
|
+
// 可在此显示应用内提示
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// 用户点击通知 → 跳转到指定 URL
|
|
118
|
+
PushNotifications.addListener('pushNotificationActionPerformed', function (action) {
|
|
119
|
+
console.log('[MobileBridge] Notification action:', JSON.stringify(action));
|
|
120
|
+
var data = action.notification && action.notification.data;
|
|
121
|
+
if (!data) return;
|
|
122
|
+
|
|
123
|
+
// 优先使用 url 字段跳转
|
|
124
|
+
if (data.url) {
|
|
125
|
+
window.location.href = data.url;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 使用 related_to 字段构建跳转 URL
|
|
130
|
+
if (data.related_to) {
|
|
131
|
+
try {
|
|
132
|
+
var related = typeof data.related_to === 'string' ? JSON.parse(data.related_to) : data.related_to;
|
|
133
|
+
if (related.o && related.ids && related.ids.length > 0) {
|
|
134
|
+
window.location.href = window.location.origin + '/app/' + related.o + '/view/' + related.ids[0];
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
} catch (e) {
|
|
138
|
+
console.error('[MobileBridge] Parse related_to error:', e);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============ 文件上传/下载 ============
|
|
145
|
+
|
|
146
|
+
function pickFile() {
|
|
147
|
+
var FilePicker = Plugins.FilePicker;
|
|
148
|
+
if (!FilePicker) return Promise.resolve(null);
|
|
149
|
+
return FilePicker.pickFiles({ multiple: false, readData: true })
|
|
150
|
+
.then(function (result) { return result.files[0]; })
|
|
151
|
+
.catch(function (e) { console.error('[MobileBridge] File pick error:', e); return null; });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function takePhoto() {
|
|
155
|
+
var Camera = Plugins.Camera;
|
|
156
|
+
if (!Camera) return Promise.resolve(null);
|
|
157
|
+
return Camera.getPhoto({
|
|
158
|
+
quality: 80, allowEditing: false, resultType: 'base64', source: 'CAMERA'
|
|
159
|
+
}).then(function (image) {
|
|
160
|
+
return {
|
|
161
|
+
data: image.base64String,
|
|
162
|
+
mimeType: 'image/' + image.format,
|
|
163
|
+
name: 'photo_' + Date.now() + '.' + image.format
|
|
164
|
+
};
|
|
165
|
+
}).catch(function (e) { console.error('[MobileBridge] Camera error:', e); return null; });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function pickImage() {
|
|
169
|
+
var Camera = Plugins.Camera;
|
|
170
|
+
if (!Camera) return Promise.resolve(null);
|
|
171
|
+
return Camera.getPhoto({
|
|
172
|
+
quality: 80, allowEditing: false, resultType: 'base64', source: 'PHOTOS'
|
|
173
|
+
}).then(function (image) {
|
|
174
|
+
return {
|
|
175
|
+
data: image.base64String,
|
|
176
|
+
mimeType: 'image/' + image.format,
|
|
177
|
+
name: 'image_' + Date.now() + '.' + image.format
|
|
178
|
+
};
|
|
179
|
+
}).catch(function (e) { console.error('[MobileBridge] Pick image error:', e); return null; });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function downloadFile(url, fileName) {
|
|
183
|
+
var Filesystem = Plugins.Filesystem;
|
|
184
|
+
if (!Filesystem) return Promise.resolve(null);
|
|
185
|
+
return fetch(url)
|
|
186
|
+
.then(function (response) { return response.blob(); })
|
|
187
|
+
.then(function (blob) {
|
|
188
|
+
return new Promise(function (resolve, reject) {
|
|
189
|
+
var reader = new FileReader();
|
|
190
|
+
reader.onload = function () {
|
|
191
|
+
var base64Data = reader.result.split(',')[1];
|
|
192
|
+
Filesystem.writeFile({
|
|
193
|
+
path: 'Downloads/' + fileName,
|
|
194
|
+
data: base64Data,
|
|
195
|
+
directory: 'DOCUMENTS',
|
|
196
|
+
recursive: true
|
|
197
|
+
}).then(function (savedFile) {
|
|
198
|
+
var SharePlugin = Plugins.Share;
|
|
199
|
+
if (SharePlugin) SharePlugin.share({ title: fileName, url: savedFile.uri });
|
|
200
|
+
resolve(savedFile);
|
|
201
|
+
}).catch(reject);
|
|
202
|
+
};
|
|
203
|
+
reader.onerror = reject;
|
|
204
|
+
reader.readAsDataURL(blob);
|
|
205
|
+
});
|
|
206
|
+
})
|
|
207
|
+
.catch(function (e) { console.error('[MobileBridge] Download error:', e); return null; });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ============ 服务器切换 ============
|
|
211
|
+
|
|
212
|
+
var STORAGE_KEY = 'steedos_urls';
|
|
213
|
+
|
|
214
|
+
function getLocalUrl() {
|
|
215
|
+
var platform = Capacitor.getPlatform();
|
|
216
|
+
if (platform === 'ios') return 'capacitor://localhost/index.html?manual=1';
|
|
217
|
+
return 'http://localhost/index.html?manual=1';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function getSavedUrls() {
|
|
221
|
+
// 优先从 cookie 读取(跨域共享)
|
|
222
|
+
var match = document.cookie.match(new RegExp('(?:^|;\\s*)' + STORAGE_KEY + '=([^;]*)'));
|
|
223
|
+
if (match) {
|
|
224
|
+
try {
|
|
225
|
+
var urls = JSON.parse(decodeURIComponent(match[1]));
|
|
226
|
+
if (Array.isArray(urls) && urls.length > 0) return urls;
|
|
227
|
+
} catch (e) {}
|
|
228
|
+
}
|
|
229
|
+
// fallback: localStorage(同域可读)
|
|
230
|
+
try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); }
|
|
231
|
+
catch (e) { return []; }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function saveUrlsFromBridge(urls) {
|
|
235
|
+
var value = encodeURIComponent(JSON.stringify(urls));
|
|
236
|
+
document.cookie = STORAGE_KEY + '=' + value + ';path=/;max-age=31536000;SameSite=None';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function switchServer() {
|
|
240
|
+
window.location.href = getLocalUrl();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 微信小程序风格:下拉页面露出深色服务器切换面板
|
|
244
|
+
(function () {
|
|
245
|
+
var startY = null;
|
|
246
|
+
var pulling = false;
|
|
247
|
+
var panel = null;
|
|
248
|
+
var safeTop = (Capacitor.getPlatform() === 'android') ? 24 : (parseInt(getComputedStyle(document.documentElement).getPropertyValue('env(safe-area-inset-top)')) || 0);
|
|
249
|
+
var headerHeight = 64 + safeTop;
|
|
250
|
+
var panelHeight = window.innerHeight - headerHeight;
|
|
251
|
+
var maxPull = panelHeight;
|
|
252
|
+
var opened = false;
|
|
253
|
+
var handle = null;
|
|
254
|
+
|
|
255
|
+
function getUrlsForPanel() {
|
|
256
|
+
var urls = getSavedUrls();
|
|
257
|
+
// 确保当前地址在列表中
|
|
258
|
+
var current = window.location.origin;
|
|
259
|
+
if (urls.indexOf(current) === -1) urls.unshift(current);
|
|
260
|
+
return urls;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function createPanel() {
|
|
264
|
+
if (panel) return;
|
|
265
|
+
|
|
266
|
+
panel = document.createElement('div');
|
|
267
|
+
panel.id = '__steedos_server_panel';
|
|
268
|
+
// 挂在 html 上,不受 body transform 影响
|
|
269
|
+
panel.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:' + headerHeight + 'px;z-index:1;' +
|
|
270
|
+
'background:linear-gradient(180deg,#1a1a2e 0%,#16213e 100%);display:flex;flex-direction:column;align-items:center;' +
|
|
271
|
+
'justify-content:center;padding:0 16px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;' +
|
|
272
|
+
'transform:translateY(-100%);';
|
|
273
|
+
|
|
274
|
+
var urls = getUrlsForPanel();
|
|
275
|
+
var currentUrl = window.location.origin;
|
|
276
|
+
|
|
277
|
+
// 上方弹性空间,把内容推到中间
|
|
278
|
+
var html = '<div style="width:100%;max-width:400px;">';
|
|
279
|
+
|
|
280
|
+
// 标题
|
|
281
|
+
html += '<div style="text-align:center;margin-bottom:14px;">' +
|
|
282
|
+
'<span style="font-size:15px;font-weight:600;color:rgba(255,255,255,0.85);">切换服务器</span></div>';
|
|
283
|
+
|
|
284
|
+
// URL 输入框 + 打开按钮
|
|
285
|
+
html += '<div style="display:flex;gap:8px;margin-bottom:14px;">' +
|
|
286
|
+
'<input id="__steedos_url_input" type="url" placeholder="输入服务器地址" ' +
|
|
287
|
+
'style="flex:1;height:38px;border-radius:8px;border:1px solid rgba(255,255,255,0.12);background:rgba(255,255,255,0.08);' +
|
|
288
|
+
'color:#fff;padding:0 12px;font-size:16px;outline:none;-webkit-appearance:none;" />' +
|
|
289
|
+
'<button id="__steedos_url_go" style="height:38px;padding:0 16px;border-radius:8px;border:none;' +
|
|
290
|
+
'background:#3b82f6;color:#fff;font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap;">打开</button>' +
|
|
291
|
+
'</div>';
|
|
292
|
+
|
|
293
|
+
// 历史地址列表
|
|
294
|
+
if (urls.length > 0) {
|
|
295
|
+
html += '<div style="margin-bottom:6px;"><span style="font-size:11px;color:rgba(255,255,255,0.35);text-transform:uppercase;letter-spacing:0.5px;">历史地址</span></div>';
|
|
296
|
+
urls.forEach(function (url) {
|
|
297
|
+
var isCurrent = currentUrl === url || url.indexOf(currentUrl) === 0 || currentUrl.indexOf(url) === 0;
|
|
298
|
+
var bg = isCurrent ? 'rgba(59,130,246,0.15)' : 'rgba(255,255,255,0.05)';
|
|
299
|
+
var border = isCurrent ? '1px solid rgba(59,130,246,0.3)' : '1px solid rgba(255,255,255,0.06)';
|
|
300
|
+
html += '<div class="__steedos_server_item" data-url="' + url + '" style="display:flex;align-items:center;' +
|
|
301
|
+
'padding:10px 12px;margin-bottom:6px;border-radius:8px;background:' + bg + ';border:' + border + ';cursor:pointer;">' +
|
|
302
|
+
'<span style="flex:1;font-size:13px;color:rgba(255,255,255,0.8);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + url + '</span>' +
|
|
303
|
+
(isCurrent ? '<span style="font-size:10px;color:#3b82f6;margin-left:6px;flex-shrink:0;">当前</span>' : '') +
|
|
304
|
+
'<span class="__steedos_remove_url" data-url="' + url + '" style="margin-left:8px;color:rgba(255,255,255,0.2);font-size:14px;cursor:pointer;flex-shrink:0;padding:0 2px;">✕</span>' +
|
|
305
|
+
'</div>';
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
html += '</div>';
|
|
310
|
+
panel.innerHTML = html;
|
|
311
|
+
|
|
312
|
+
// 拉手指示条 — 独立元素,z-index:3 高于 body(2),固定在 body 顶部上方
|
|
313
|
+
handle = document.createElement('div');
|
|
314
|
+
handle.id = '__steedos_handle';
|
|
315
|
+
handle.style.cssText = 'position:fixed;left:0;right:0;top:0;z-index:3;text-align:center;padding:8px 0;' +
|
|
316
|
+
'pointer-events:none;transform:translateY(-20px);';
|
|
317
|
+
handle.innerHTML = '<div style="width:40px;height:5px;border-radius:3px;background:rgba(255,255,255,0.5);margin:0 auto;"></div>';
|
|
318
|
+
document.documentElement.appendChild(handle);
|
|
319
|
+
|
|
320
|
+
// 输入框预填
|
|
321
|
+
var input = panel.querySelector('#__steedos_url_input');
|
|
322
|
+
if (urls.length > 0) input.value = urls[0];
|
|
323
|
+
|
|
324
|
+
// 打开按钮
|
|
325
|
+
panel.querySelector('#__steedos_url_go').addEventListener('click', function (e) {
|
|
326
|
+
e.stopPropagation();
|
|
327
|
+
var url = input.value.trim();
|
|
328
|
+
if (!url) return;
|
|
329
|
+
if (url.indexOf('http') !== 0) url = 'https://' + url;
|
|
330
|
+
var saved = getSavedUrls().filter(function (u) { return u !== url; });
|
|
331
|
+
saved.unshift(url);
|
|
332
|
+
saveUrlsFromBridge(saved.slice(0, 10));
|
|
333
|
+
window.location.href = url;
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
input.addEventListener('keydown', function (e) {
|
|
337
|
+
if (e.key === 'Enter') { e.preventDefault(); panel.querySelector('#__steedos_url_go').click(); }
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// 点击跳转
|
|
341
|
+
panel.querySelectorAll('.__steedos_server_item').forEach(function (el) {
|
|
342
|
+
el.addEventListener('click', function (e) {
|
|
343
|
+
if (e.target.classList.contains('__steedos_remove_url')) return;
|
|
344
|
+
e.stopPropagation();
|
|
345
|
+
var url = el.getAttribute('data-url');
|
|
346
|
+
if (url) window.location.href = url;
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// 删除
|
|
351
|
+
panel.querySelectorAll('.__steedos_remove_url').forEach(function (el) {
|
|
352
|
+
el.addEventListener('click', function (e) {
|
|
353
|
+
e.stopPropagation();
|
|
354
|
+
var url = el.getAttribute('data-url');
|
|
355
|
+
var saved = getSavedUrls().filter(function (u) { return u !== url; });
|
|
356
|
+
saveUrlsFromBridge(saved);
|
|
357
|
+
removePanel();
|
|
358
|
+
createPanel();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
panel.addEventListener('touchmove', function (e) { e.stopPropagation(); }, { passive: false });
|
|
363
|
+
|
|
364
|
+
// 挂到 <html> 上,不受 body transform 影响
|
|
365
|
+
document.documentElement.appendChild(panel);
|
|
366
|
+
|
|
367
|
+
document.body.style.position = 'relative';
|
|
368
|
+
document.body.style.zIndex = '2';
|
|
369
|
+
var bodyBg = getComputedStyle(document.body).backgroundColor;
|
|
370
|
+
if (!bodyBg || bodyBg === 'rgba(0, 0, 0, 0)' || bodyBg === 'transparent') {
|
|
371
|
+
document.body.style.background = '#fff';
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function removePanel() {
|
|
376
|
+
if (panel && panel.parentNode) panel.parentNode.removeChild(panel);
|
|
377
|
+
panel = null;
|
|
378
|
+
if (handle && handle.parentNode) handle.parentNode.removeChild(handle);
|
|
379
|
+
handle = null;
|
|
380
|
+
document.body.style.position = '';
|
|
381
|
+
document.body.style.zIndex = '';
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
var panelHeaderStyle = null;
|
|
385
|
+
|
|
386
|
+
function setHeaderPadding(enabled) {
|
|
387
|
+
if (enabled) {
|
|
388
|
+
// 删除覆盖样式,恢复默认
|
|
389
|
+
if (panelHeaderStyle && panelHeaderStyle.parentNode) {
|
|
390
|
+
panelHeaderStyle.parentNode.removeChild(panelHeaderStyle);
|
|
391
|
+
panelHeaderStyle = null;
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
// 注入覆盖样式,去掉 safe area
|
|
395
|
+
if (!panelHeaderStyle) {
|
|
396
|
+
panelHeaderStyle = document.createElement('style');
|
|
397
|
+
panelHeaderStyle.textContent =
|
|
398
|
+
'.steedos-header-container-line-one { height: 64px !important; padding-top: 0 !important; }';
|
|
399
|
+
document.head.appendChild(panelHeaderStyle);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function closePanel() {
|
|
405
|
+
// 面板向上滑出 + body 回到原位
|
|
406
|
+
if (panel) {
|
|
407
|
+
panel.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)';
|
|
408
|
+
panel.style.transform = 'translateY(-100%)';
|
|
409
|
+
}
|
|
410
|
+
if (handle) { handle.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)'; handle.style.transform = 'translateY(-20px)'; }
|
|
411
|
+
document.body.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)';
|
|
412
|
+
document.body.style.transform = '';
|
|
413
|
+
document.body.style.boxShadow = '';
|
|
414
|
+
setHeaderPadding(true);
|
|
415
|
+
opened = false;
|
|
416
|
+
if (closePushCleanup) { closePushCleanup(); closePushCleanup = null; }
|
|
417
|
+
setTimeout(function () {
|
|
418
|
+
removePanel();
|
|
419
|
+
document.body.style.transition = '';
|
|
420
|
+
}, 300);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// 面板打开后,注册关闭交互(点击 + 向上推)
|
|
424
|
+
var closePushCleanup = null;
|
|
425
|
+
|
|
426
|
+
function registerCloseHandlers() {
|
|
427
|
+
var pushStartY = null;
|
|
428
|
+
var pushActive = false;
|
|
429
|
+
|
|
430
|
+
var touchStartHandler = function (e) {
|
|
431
|
+
if (panel && panel.contains(e.target)) return;
|
|
432
|
+
pushStartY = e.touches[0].clientY;
|
|
433
|
+
pushActive = false;
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
var touchMoveHandler = function (e) {
|
|
437
|
+
if (pushStartY === null) return;
|
|
438
|
+
var deltaY = e.touches[0].clientY - pushStartY;
|
|
439
|
+
if (deltaY < -10) pushActive = true;
|
|
440
|
+
if (pushActive) {
|
|
441
|
+
var offset = Math.max(0, maxPull + deltaY);
|
|
442
|
+
document.body.style.transform = 'translateY(' + offset + 'px)';
|
|
443
|
+
document.body.style.transition = 'none';
|
|
444
|
+
if (handle) { handle.style.transform = 'translateY(' + (offset - 20) + 'px)'; handle.style.transition = 'none'; }
|
|
445
|
+
// 面板也跟着动:从0开始往上移
|
|
446
|
+
if (panel) {
|
|
447
|
+
var panelOffset = offset - maxPull;
|
|
448
|
+
panel.style.transform = 'translateY(' + panelOffset + 'px)';
|
|
449
|
+
panel.style.transition = 'none';
|
|
450
|
+
}
|
|
451
|
+
e.preventDefault();
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
var touchEndHandler = function () {
|
|
456
|
+
if (pushActive) {
|
|
457
|
+
var transform = document.body.style.transform;
|
|
458
|
+
var m = transform && transform.match(/translateY\(([\d.]+)px\)/);
|
|
459
|
+
var offset = m ? parseFloat(m[1]) : maxPull;
|
|
460
|
+
if (offset < maxPull * 0.7) {
|
|
461
|
+
// 关闭
|
|
462
|
+
closePanel();
|
|
463
|
+
} else {
|
|
464
|
+
// 弹回打开状态
|
|
465
|
+
document.body.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)';
|
|
466
|
+
document.body.style.transform = 'translateY(' + maxPull + 'px)';
|
|
467
|
+
if (handle) { handle.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)'; handle.style.transform = 'translateY(' + (maxPull - 20) + 'px)'; }
|
|
468
|
+
if (panel) {
|
|
469
|
+
panel.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)';
|
|
470
|
+
panel.style.transform = '';
|
|
471
|
+
}
|
|
472
|
+
// 不 cleanup — 保持 handler 可以再次操作
|
|
473
|
+
}
|
|
474
|
+
} else if (pushStartY !== null) {
|
|
475
|
+
// 纯点击 → 收回
|
|
476
|
+
closePanel();
|
|
477
|
+
}
|
|
478
|
+
pushStartY = null;
|
|
479
|
+
pushActive = false;
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
var clickHandler = function (e) {
|
|
483
|
+
if (panel && !panel.contains(e.target)) {
|
|
484
|
+
e.preventDefault();
|
|
485
|
+
e.stopPropagation();
|
|
486
|
+
e.stopImmediatePropagation();
|
|
487
|
+
closePanel();
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
closePushCleanup = function () {
|
|
492
|
+
document.removeEventListener('touchstart', touchStartHandler, true);
|
|
493
|
+
document.removeEventListener('touchmove', touchMoveHandler, true);
|
|
494
|
+
document.removeEventListener('touchend', touchEndHandler, true);
|
|
495
|
+
document.removeEventListener('click', clickHandler, true);
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
document.addEventListener('touchstart', touchStartHandler, true);
|
|
499
|
+
document.addEventListener('touchmove', touchMoveHandler, true);
|
|
500
|
+
document.addEventListener('touchend', touchEndHandler, true);
|
|
501
|
+
document.addEventListener('click', clickHandler, true);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function isAtTop() {
|
|
505
|
+
return window.scrollY <= 0 && document.documentElement.scrollTop <= 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
var isAndroidPlatform = Capacitor.getPlatform() === 'android';
|
|
509
|
+
|
|
510
|
+
document.addEventListener('touchstart', function (e) {
|
|
511
|
+
if (opened) return;
|
|
512
|
+
if (e.touches.length === 1 && isAtTop()) {
|
|
513
|
+
// Android 上只允许从 header 区域开始下拉,避免正常滚动误触
|
|
514
|
+
if (isAndroidPlatform && e.touches[0].clientY > headerHeight) {
|
|
515
|
+
startY = null;
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
startY = e.touches[0].clientY;
|
|
519
|
+
pulling = false;
|
|
520
|
+
} else {
|
|
521
|
+
startY = null;
|
|
522
|
+
}
|
|
523
|
+
}, { passive: true });
|
|
524
|
+
|
|
525
|
+
document.addEventListener('touchmove', function (e) {
|
|
526
|
+
if (startY === null || opened) return;
|
|
527
|
+
var deltaY = e.touches[0].clientY - startY;
|
|
528
|
+
|
|
529
|
+
if (deltaY < 0) {
|
|
530
|
+
if (pulling) {
|
|
531
|
+
document.body.style.transform = '';
|
|
532
|
+
document.body.style.transition = '';
|
|
533
|
+
removePanel();
|
|
534
|
+
pulling = false;
|
|
535
|
+
}
|
|
536
|
+
startY = null;
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// 未开始拉动时,如果页面已经不在顶部了(用户在向上滚动后反弹),取消
|
|
541
|
+
if (!pulling && !isAtTop()) {
|
|
542
|
+
startY = null;
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (deltaY > 30 && !pulling) {
|
|
547
|
+
pulling = true;
|
|
548
|
+
createPanel();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (pulling) {
|
|
552
|
+
var offset = Math.min(deltaY * 0.85, maxPull);
|
|
553
|
+
document.body.style.transform = 'translateY(' + offset + 'px)';
|
|
554
|
+
document.body.style.transition = 'none';
|
|
555
|
+
document.body.style.boxShadow = '0 -4px 30px rgba(0,0,0,0.3)';
|
|
556
|
+
if (handle) { handle.style.transform = 'translateY(' + (offset - 20) + 'px)'; handle.style.transition = 'none'; }
|
|
557
|
+
// 面板从上方滑入:初始在 -100%,随拉动逐渐到 0
|
|
558
|
+
if (panel) {
|
|
559
|
+
var panelProgress = offset / maxPull;
|
|
560
|
+
var panelTranslate = -100 + (panelProgress * 100);
|
|
561
|
+
panel.style.transform = 'translateY(' + panelTranslate + '%)';
|
|
562
|
+
panel.style.transition = 'none';
|
|
563
|
+
}
|
|
564
|
+
e.preventDefault();
|
|
565
|
+
}
|
|
566
|
+
}, { passive: false });
|
|
567
|
+
|
|
568
|
+
document.addEventListener('touchend', function () {
|
|
569
|
+
if (!pulling) { startY = null; return; }
|
|
570
|
+
|
|
571
|
+
var currentOffset = 0;
|
|
572
|
+
var transform = document.body.style.transform;
|
|
573
|
+
var match = transform && transform.match(/translateY\(([\d.]+)px\)/);
|
|
574
|
+
if (match) currentOffset = parseFloat(match[1]);
|
|
575
|
+
|
|
576
|
+
if (currentOffset >= maxPull * 0.5) {
|
|
577
|
+
document.body.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)';
|
|
578
|
+
document.body.style.transform = 'translateY(' + maxPull + 'px)';
|
|
579
|
+
if (handle) { handle.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)'; handle.style.transform = 'translateY(' + (maxPull - 20) + 'px)'; }
|
|
580
|
+
if (panel) {
|
|
581
|
+
panel.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)';
|
|
582
|
+
panel.style.transform = '';
|
|
583
|
+
}
|
|
584
|
+
opened = true;
|
|
585
|
+
setHeaderPadding(false);
|
|
586
|
+
setTimeout(registerCloseHandlers, 350);
|
|
587
|
+
} else {
|
|
588
|
+
// 面板滑回上方
|
|
589
|
+
if (panel) {
|
|
590
|
+
panel.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)';
|
|
591
|
+
panel.style.transform = 'translateY(-100%)';
|
|
592
|
+
}
|
|
593
|
+
if (handle) { handle.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)'; handle.style.transform = 'translateY(-20px)'; }
|
|
594
|
+
document.body.style.transition = 'transform 0.3s cubic-bezier(0.2,0,0,1)';
|
|
595
|
+
document.body.style.transform = '';
|
|
596
|
+
document.body.style.boxShadow = '';
|
|
597
|
+
setTimeout(function () {
|
|
598
|
+
removePanel();
|
|
599
|
+
document.body.style.transition = '';
|
|
600
|
+
}, 300);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
startY = null;
|
|
604
|
+
pulling = false;
|
|
605
|
+
}, { passive: true });
|
|
606
|
+
})();
|
|
607
|
+
|
|
608
|
+
// ============ 暴露全局 Bridge ============
|
|
609
|
+
|
|
610
|
+
window.SteedosBridge = {
|
|
611
|
+
pickFile: pickFile,
|
|
612
|
+
takePhoto: takePhoto,
|
|
613
|
+
pickImage: pickImage,
|
|
614
|
+
downloadFile: downloadFile,
|
|
615
|
+
getPushToken: function () { return localStorage.getItem('push_token'); },
|
|
616
|
+
registerPushDevice: function () { var t = localStorage.getItem('push_token'); if (t) registerPushDevice(t); },
|
|
617
|
+
unregisterPushDevice: unregisterPushDevice,
|
|
618
|
+
isNative: function () { return true; },
|
|
619
|
+
getPlatform: function () { return Capacitor.getPlatform(); },
|
|
620
|
+
switchServer: switchServer,
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
// ============ 拦截链接点击和 window.open(防止跳转外部浏览器)============
|
|
624
|
+
|
|
625
|
+
function isFileUrl(url) {
|
|
626
|
+
return /\/(api\/files|cfs\/files|api\/v6\/files|s3\/|api\/v1\/files)\//i.test(url) ||
|
|
627
|
+
/[?&]download=true/i.test(url) ||
|
|
628
|
+
/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|zip|rar|png|jpg|jpeg|gif|csv|txt|json)(\?|$)/i.test(url);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function getFileName(url) {
|
|
632
|
+
var name = url.split('/').pop().split('?')[0] || ('file_' + Date.now());
|
|
633
|
+
try { name = decodeURIComponent(name); } catch (e) {}
|
|
634
|
+
return name;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// 拦截 <a target="_blank"> 点击
|
|
638
|
+
document.addEventListener('click', function (e) {
|
|
639
|
+
var anchor = e.target.closest ? e.target.closest('a') : null;
|
|
640
|
+
if (!anchor) return;
|
|
641
|
+
|
|
642
|
+
var href = anchor.getAttribute('href');
|
|
643
|
+
if (!href || href === '#') return;
|
|
644
|
+
|
|
645
|
+
var target = anchor.getAttribute('target');
|
|
646
|
+
var fullUrl = new URL(href, window.location.href).href;
|
|
647
|
+
|
|
648
|
+
// 文件下载链接 → 原生下载
|
|
649
|
+
if (isFileUrl(fullUrl)) {
|
|
650
|
+
e.preventDefault();
|
|
651
|
+
e.stopPropagation();
|
|
652
|
+
console.log('[MobileBridge] Intercepted file download link:', fullUrl);
|
|
653
|
+
downloadFile(fullUrl, getFileName(fullUrl));
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// target="_blank" → 在当前 WebView 内导航
|
|
658
|
+
if (target === '_blank') {
|
|
659
|
+
e.preventDefault();
|
|
660
|
+
e.stopPropagation();
|
|
661
|
+
console.log('[MobileBridge] Intercepted _blank link, navigating in WebView:', fullUrl);
|
|
662
|
+
window.location.href = fullUrl;
|
|
663
|
+
}
|
|
664
|
+
}, true);
|
|
665
|
+
|
|
666
|
+
// 拦截 window.open
|
|
667
|
+
var _originalOpen = window.open;
|
|
668
|
+
window.open = function (url) {
|
|
669
|
+
if (!url) return null;
|
|
670
|
+
var urlStr = String(url);
|
|
671
|
+
if (isFileUrl(urlStr)) {
|
|
672
|
+
console.log('[MobileBridge] Intercepted window.open file download:', urlStr);
|
|
673
|
+
downloadFile(urlStr, getFileName(urlStr));
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
console.log('[MobileBridge] Intercepted window.open, navigating in WebView:', urlStr);
|
|
677
|
+
window.location.href = urlStr;
|
|
678
|
+
return null;
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// ============ 角标清除 ============
|
|
682
|
+
|
|
683
|
+
function clearBadge() {
|
|
684
|
+
var PushNotifications = Plugins.PushNotifications;
|
|
685
|
+
if (!PushNotifications) return;
|
|
686
|
+
// 清除通知中心所有已送达通知
|
|
687
|
+
if (PushNotifications.removeAllDeliveredNotifications) {
|
|
688
|
+
PushNotifications.removeAllDeliveredNotifications();
|
|
689
|
+
}
|
|
690
|
+
// 设置角标为 0
|
|
691
|
+
var Badge = Plugins.Badge;
|
|
692
|
+
if (Badge && Badge.clear) {
|
|
693
|
+
Badge.clear();
|
|
694
|
+
}
|
|
695
|
+
// iOS 备选方案:通过 PushNotifications 设置 badge
|
|
696
|
+
if (PushNotifications.setBadgeCount) {
|
|
697
|
+
PushNotifications.setBadgeCount({ count: 0 });
|
|
698
|
+
}
|
|
699
|
+
console.log('[MobileBridge] Badge cleared');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// App 启动时清除
|
|
703
|
+
clearBadge();
|
|
704
|
+
|
|
705
|
+
// App 从后台回到前台时清除
|
|
706
|
+
document.addEventListener('visibilitychange', function () {
|
|
707
|
+
if (!document.hidden) {
|
|
708
|
+
clearBadge();
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
// Capacitor App plugin 的 resume 事件(更可靠)
|
|
713
|
+
var AppPlugin = Plugins.App;
|
|
714
|
+
if (AppPlugin && AppPlugin.addListener) {
|
|
715
|
+
AppPlugin.addListener('appStateChange', function (state) {
|
|
716
|
+
if (state.isActive) {
|
|
717
|
+
clearBadge();
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// ============ 状态栏 ============
|
|
723
|
+
|
|
724
|
+
function initStatusBar() {
|
|
725
|
+
console.log('[MobileBridge] initStatusBar called');
|
|
726
|
+
var isAndroid = Capacitor.getPlatform() === 'android';
|
|
727
|
+
var isIOS = Capacitor.getPlatform() === 'ios';
|
|
728
|
+
|
|
729
|
+
if (isIOS) {
|
|
730
|
+
// iOS: 通过 StatusBar 插件设置 overlay 模式
|
|
731
|
+
var StatusBar = Plugins.StatusBar;
|
|
732
|
+
if (StatusBar) {
|
|
733
|
+
StatusBar.setOverlaysWebView({ overlay: true });
|
|
734
|
+
StatusBar.setStyle({ style: 'LIGHT' });
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// 确保 viewport 有 viewport-fit=cover
|
|
738
|
+
var viewport = document.querySelector('meta[name="viewport"]');
|
|
739
|
+
if (viewport) {
|
|
740
|
+
var content = viewport.getAttribute('content') || '';
|
|
741
|
+
if (content.indexOf('viewport-fit') === -1) {
|
|
742
|
+
viewport.setAttribute('content', content + ', viewport-fit=cover');
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
viewport = document.createElement('meta');
|
|
746
|
+
viewport.name = 'viewport';
|
|
747
|
+
viewport.content = 'width=device-width, initial-scale=1.0, viewport-fit=cover';
|
|
748
|
+
document.head.appendChild(viewport);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// iOS 用 env(safe-area-inset-top) 给 header 加 padding
|
|
752
|
+
var safeAreaTop = 'env(safe-area-inset-top)';
|
|
753
|
+
var style = document.createElement('style');
|
|
754
|
+
style.textContent =
|
|
755
|
+
'.steedos-header-container-line-one { height: calc(64px + ' + safeAreaTop + ') !important; padding-top: ' + safeAreaTop + ' !important; }' +
|
|
756
|
+
'.creator-content-wrapper { margin-top: calc(64px + ' + safeAreaTop + ') !important; }' +
|
|
757
|
+
'.antd-Modal-content { padding-top: calc(24px + ' + safeAreaTop + ') !important; }' +
|
|
758
|
+
'.antd-Modal-close { top: ' + safeAreaTop + ' !important; }' +
|
|
759
|
+
'.steedos-instance-detail-wrapper { height: calc(100vh - 64px - ' + safeAreaTop + ') !important; height: calc(100dvh - 64px - ' + safeAreaTop + ') !important; }' +
|
|
760
|
+
'.ant-notification, .antd-Toast-wrap { top: ' + safeAreaTop + ' !important; padding-top: ' + safeAreaTop + ' !important; }' +
|
|
761
|
+
'.ant-notification-notice, .antd-Toast-notice { margin-top: 8px !important; }' +
|
|
762
|
+
'[data-radix-popper-content-wrapper] { max-width: calc(100vw - 16px) !important; }' +
|
|
763
|
+
'[data-radix-popper-content-wrapper] [role="dialog"] { max-width: calc(100vw - 16px) !important; overflow-x: hidden !important; }' +
|
|
764
|
+
'[data-radix-popper-content-wrapper] .tb-inbox { width: calc(100vw - 16px) !important; max-width: calc(100vw - 16px) !important; }' +
|
|
765
|
+
'.ant-drawer .ant-drawer-content { padding-top: ' + safeAreaTop + ' !important; }' +
|
|
766
|
+
'.antd-Drawer-footer { padding-left: 30px !important; padding-right: 30px !important; justify-content: space-between !important; }' +
|
|
767
|
+
'.steedos-approve-submit-button { order: 2 !important; }' +
|
|
768
|
+
'.steedos-approve-close-button { order: 1 !important; }';
|
|
769
|
+
document.head.appendChild(style);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Android: WebView 已通过 overlay=false 避开状态栏,不再给 header 额外加顶部空白
|
|
773
|
+
if (isAndroid) {
|
|
774
|
+
var StatusBar = Plugins.StatusBar;
|
|
775
|
+
if (StatusBar) {
|
|
776
|
+
StatusBar.setOverlaysWebView({ overlay: false });
|
|
777
|
+
StatusBar.setStyle({ style: 'LIGHT' });
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
var style = document.createElement('style');
|
|
781
|
+
style.textContent =
|
|
782
|
+
'.steedos-header-container-line-one { height: 64px !important; padding-top: 0 !important; }' +
|
|
783
|
+
'.creator-content-wrapper { margin-top: 64px !important; }' +
|
|
784
|
+
'.antd-Modal-content { padding-top: 24px !important; }' +
|
|
785
|
+
'.antd-Modal-close { top: 0 !important; }' +
|
|
786
|
+
'.steedos-instance-detail-wrapper { height: calc(100vh - 64px) !important; height: calc(100dvh - 64px) !important; }' +
|
|
787
|
+
'.ant-notification, .antd-Toast-wrap { top: 0 !important; padding-top: 0 !important; }' +
|
|
788
|
+
'.ant-notification-notice, .antd-Toast-notice { margin-top: 8px !important; }' +
|
|
789
|
+
'[data-radix-popper-content-wrapper] { max-width: calc(100vw - 16px) !important; }' +
|
|
790
|
+
'[data-radix-popper-content-wrapper] [role="dialog"] { max-width: calc(100vw - 16px) !important; overflow-x: hidden !important; }' +
|
|
791
|
+
'[data-radix-popper-content-wrapper] .tb-inbox { width: calc(100vw - 16px) !important; max-width: calc(100vw - 16px) !important; }' +
|
|
792
|
+
'.ant-drawer .ant-drawer-content { padding-top: 0 !important; }' +
|
|
793
|
+
'.antd-Drawer-footer { padding-left: 30px !important; padding-right: 30px !important; justify-content: space-between !important; }' +
|
|
794
|
+
'.steedos-approve-submit-button { order: 2 !important; }' +
|
|
795
|
+
'.steedos-approve-close-button { order: 1 !important; }';
|
|
796
|
+
document.head.appendChild(style);
|
|
797
|
+
}
|
|
798
|
+
console.log('[MobileBridge] StatusBar initialized, platform:', Capacitor.getPlatform());
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function initIOSLoginSafeArea() {
|
|
802
|
+
if (Capacitor.getPlatform() !== 'ios') return;
|
|
803
|
+
|
|
804
|
+
var style = document.createElement('style');
|
|
805
|
+
style.textContent =
|
|
806
|
+
'@supports (padding-top: env(safe-area-inset-top)) {' +
|
|
807
|
+
'.steedos-auth-card { margin-top: max(env(safe-area-inset-top), 28px) !important; }' +
|
|
808
|
+
'}';
|
|
809
|
+
document.head.appendChild(style);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function initMobileRadixPopoverLayout() {
|
|
813
|
+
var platform = Capacitor.getPlatform();
|
|
814
|
+
if (platform !== 'ios' && platform !== 'android') return;
|
|
815
|
+
|
|
816
|
+
function getTranslateY(transform) {
|
|
817
|
+
if (!transform || transform === 'none') return 0;
|
|
818
|
+
var matrix3d = transform.match(/^matrix3d\((.+)\)$/);
|
|
819
|
+
if (matrix3d) {
|
|
820
|
+
var values3d = matrix3d[1].split(',').map(parseFloat);
|
|
821
|
+
return values3d[13] || 0;
|
|
822
|
+
}
|
|
823
|
+
var matrix = transform.match(/^matrix\((.+)\)$/);
|
|
824
|
+
if (matrix) {
|
|
825
|
+
var values = matrix[1].split(',').map(parseFloat);
|
|
826
|
+
return values[5] || 0;
|
|
827
|
+
}
|
|
828
|
+
var translate = transform.match(/translate(?:3d)?\([^,]+,\s*([-\d.]+)px/);
|
|
829
|
+
return translate ? parseFloat(translate[1]) : 0;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function fixRadixPopovers() {
|
|
833
|
+
var wrappers = document.querySelectorAll('[data-radix-popper-content-wrapper]');
|
|
834
|
+
wrappers.forEach(function (wrapper) {
|
|
835
|
+
var inbox = wrapper.querySelector('.tb-inbox');
|
|
836
|
+
if (!inbox) return;
|
|
837
|
+
|
|
838
|
+
var computed = window.getComputedStyle(wrapper);
|
|
839
|
+
var y = getTranslateY(computed.transform || wrapper.style.transform);
|
|
840
|
+
setStyle(wrapper, 'left', '0px');
|
|
841
|
+
setStyle(wrapper, 'right', 'auto');
|
|
842
|
+
setStyle(wrapper, 'maxWidth', 'calc(100vw - 16px)');
|
|
843
|
+
setStyle(wrapper, 'transform', 'translate(8px, ' + y + 'px)');
|
|
844
|
+
setCssVar(wrapper, '--radix-popper-available-width', 'calc(100vw - 16px)');
|
|
845
|
+
|
|
846
|
+
setStyle(inbox, 'width', 'calc(100vw - 16px)');
|
|
847
|
+
setStyle(inbox, 'maxWidth', 'calc(100vw - 16px)');
|
|
848
|
+
setStyle(inbox, 'overflowX', 'hidden');
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function setStyle(element, prop, value) {
|
|
853
|
+
if (element.style[prop] !== value) {
|
|
854
|
+
element.style[prop] = value;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function setCssVar(element, prop, value) {
|
|
859
|
+
if (element.style.getPropertyValue(prop) !== value) {
|
|
860
|
+
element.style.setProperty(prop, value);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
var scheduled = false;
|
|
865
|
+
function scheduleFix() {
|
|
866
|
+
if (scheduled) return;
|
|
867
|
+
scheduled = true;
|
|
868
|
+
requestAnimationFrame(function () {
|
|
869
|
+
scheduled = false;
|
|
870
|
+
fixRadixPopovers();
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
scheduleFix();
|
|
875
|
+
document.addEventListener('click', function () { setTimeout(scheduleFix, 0); }, true);
|
|
876
|
+
window.addEventListener('resize', scheduleFix);
|
|
877
|
+
new MutationObserver(scheduleFix).observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] });
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// ============ 初始化 ============
|
|
881
|
+
|
|
882
|
+
initStatusBar();
|
|
883
|
+
initIOSLoginSafeArea();
|
|
884
|
+
initMobileRadixPopoverLayout();
|
|
885
|
+
initPushNotifications();
|
|
886
|
+
console.log('[MobileBridge] Bridge initialized. window.SteedosBridge available.');
|
|
887
|
+
|
|
888
|
+
}
|
|
889
|
+
boot();
|
|
890
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steedos/service-ui",
|
|
3
|
-
"version": "3.0.15-beta.
|
|
3
|
+
"version": "3.0.15-beta.21",
|
|
4
4
|
"main": "package.service.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"steedos"
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
"description": "steedos package",
|
|
12
12
|
"repository": {},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@steedos/auth": "3.0.15-beta.
|
|
15
|
-
"@steedos/i18n": "3.0.15-beta.
|
|
16
|
-
"@steedos/objectql": "3.0.15-beta.
|
|
14
|
+
"@steedos/auth": "3.0.15-beta.21",
|
|
15
|
+
"@steedos/i18n": "3.0.15-beta.21",
|
|
16
|
+
"@steedos/objectql": "3.0.15-beta.21",
|
|
17
17
|
"express": "^5.1.0"
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
|
-
"gitHead": "
|
|
23
|
+
"gitHead": "0c91a6ec64fe742fdb7e4d9fda5ffa310e346654"
|
|
24
24
|
}
|