kuaifan 84f225f3f3 feat(mobile): 兼容新 Expo 壳(dootask_expo UA)
配合 dootask-app 仓库的 Expo 迁移(见 docs/migration-eeui-to-expo.md 的 Phase 5),
让服务端和前端同时识别旧 EEUI 壳与新 Expo 壳的 User-Agent,并让 eeui.js 的同步返回
方法在 Expo 壳下优先读取 injectedJS 启动时写入的 __EXPO_INIT_DATA__ / __EXPO_VARIATES__
缓存,避免原本同步 API 变成 Promise 后破坏调用方。

后端:
- Base::isEEUIApp():同时匹配 kuaifan_eeui / dootask_expo
- UserDevice:android_(kuaifan_eeui|dootask_expo) 正则捕获标识段,版本号按实际段名取
- IndexController PDF 预览:浏览器分类兼容 android_dootask_expo / ios_dootask_expo
- SystemController::prefetch:$isApp 同时接受两种 UA
- resources/views/download.blade.php:/eeui|dootask_expo/i

前端:
- app.js:
  - isEEUIApp 正则新增 dootask_expo
  - $preload 等待条件改为 requireModuleJs 可用 OR window.__EXPO_BRIDGE_READY__,
    避免 Expo 壳下等 15 秒超时
- eeui.js:以下几个同步 getter 在 Expo 壳下先读 window.__EXPO_* 再回落到原生:
  - eeuiAppVersion / eeuiAppLocalVersion → __EXPO_INIT_DATA__.version
  - eeuiAppGetPageInfo → __EXPO_INIT_DATA__.pageInfo
  - eeuiAppGetThemeName → __EXPO_INIT_DATA__.themeName
  - eeuiAppKeyboardStatus → __EXPO_INIT_DATA__.keyboardVisible
  - eeuiAppGetVariate → __EXPO_VARIATES__[key]
  - eeuiAppGetCachesString → __EXPO_CACHES__[key](RN 侧后续要同步 broadcast)

旧 EEUI 壳不受影响:只读缓存不存在时自动回落到原有 $A.eeuiModule() 调用,
行为与改动前一致。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 09:44:53 +00:00

397 lines
14 KiB
JavaScript
Executable File
Vendored
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* EEUI App 专用
*/
import {languageName} from "../language";
(function (window) {
const $ = window.$A;
/**
* =============================================================================
* **************************** EEUI App extra *****************************
* =============================================================================
*/
$.extend({
// 获取eeui模块
eeuiModule(name = 'eeui') {
if (typeof requireModuleJs === "function") {
return requireModuleJs(name);
}
return null;
},
// 获取eeui模块Promise
eeuiModulePromise(name = 'eeui') {
return new Promise((resolve, reject) => {
try {
const eeui = $A.eeuiModule(name);
if (!eeui) {
return reject({msg: "module not found"});
}
resolve(eeui);
} catch (e) {
reject({msg: e.message});
}
})
},
// 获取eeui版本号Expo 壳下改为读取启动时注入的 __EXPO_INIT_DATA__兼容旧 EEUI
eeuiAppVersion() {
return window.__EXPO_INIT_DATA__?.version ?? $A.eeuiModule()?.getVersion();
},
// 获取本地软件版本号
eeuiAppLocalVersion() {
return window.__EXPO_INIT_DATA__?.version ?? $A.eeuiModule()?.getLocalVersion();
},
// Alert
eeuiAppAlert(object, callback) {
if (typeof callback !== "function") callback = _ => {};
$A.eeuiModule()?.alert(object, callback);
},
// Toast
eeuiAppToast(object) {
$A.eeuiModule()?.toast(object);
},
// 相对地址基于当前地址补全
eeuiAppRewriteUrl(val) {
return $A.eeuiModule()?.rewriteUrl(val);
},
// 获取页面信息Expo 壳在 injectedJS 启动时写入 __EXPO_INIT_DATA__.pageInfo
eeuiAppGetPageInfo(pageName) {
const cached = window.__EXPO_INIT_DATA__?.pageInfo;
if (cached) {
return pageName ? { ...cached, pageName } : cached;
}
return $A.eeuiModule()?.getPageInfo(pageName || "");
},
// 打开app新页面
eeuiAppOpenPage(object, callback) {
if (typeof callback !== "function") {
callback = _ => {};
}
if (typeof object.callback === "function") {
callback = object.callback;
delete object.callback
}
$A.eeuiModule()?.openPage(Object.assign({
softInputMode: "resize",
}, object), callback);
},
// 使用系统浏览器打开网页
eeuiAppOpenWeb(url) {
$A.eeuiModule()?.openWeb(url)
},
// 拦截返回按键事件仅支持android、iOS无效
eeuiAppSetPageBackPressed(object, callback) {
if (typeof callback !== "function") callback = _ => {};
$A.eeuiModule()?.setPageBackPressed(object, callback);
},
// 返回手机桌面
eeuiAppGoDesktop() {
$A.eeuiModule()?.goDesktop();
},
// 打开屏幕常亮
eeuiAppKeepScreenOn() {
$A.eeuiModule()?.keepScreenOn();
},
// 关闭屏幕常亮
eeuiAppKeepScreenOff() {
$A.eeuiModule()?.keepScreenOff();
},
// 隐藏软键盘
eeuiAppKeyboardHide() {
$A.eeuiModule()?.keyboardHide();
},
// 给app发送消息
eeuiAppSendMessage(object) {
$A.eeuiModule("webview")?.sendMessage(object);
},
// 设置浏览器地址
eeuiAppSetUrl(url) {
$A.eeuiModule("webview")?.setUrl(url);
},
// 生成webview快照
eeuiAppGetWebviewSnapshot(callback) {
$A.eeuiModule("webview")?.createSnapshot(callback);
},
// 显示webview快照
eeuiAppShowWebviewSnapshot() {
$A.eeuiModule("webview")?.showSnapshot();
},
// 隐藏webview快照
eeuiAppHideWebviewSnapshot() {
$A.eeuiModule("webview")?.hideSnapshot();
},
// 扫码
eeuiAppScan(callback) {
$A.eeuiModule()?.openScaner({}, (res) => {
switch (res.status) {
case "success":
callback(res.text);
break;
}
});
},
// 检查更新
eeuiAppCheckUpdate() {
$A.eeuiModule()?.checkUpdate();
},
// 获取主题名称 light|darkExpo 壳:启动时注入 + 系统变更推送更新 __EXPO_INIT_DATA__.themeName
eeuiAppGetThemeName() {
return window.__EXPO_INIT_DATA__?.themeName ?? $A.eeuiModule()?.getThemeName();
},
// 判断软键盘是否可见Expo 壳keyboardDidShow/Hide 会同步更新 __EXPO_INIT_DATA__.keyboardVisible
eeuiAppKeyboardStatus() {
if (window.__EXPO_INIT_DATA__ && typeof window.__EXPO_INIT_DATA__.keyboardVisible === "boolean") {
return window.__EXPO_INIT_DATA__.keyboardVisible;
}
return $A.eeuiModule()?.keyboardStatus();
},
// 设置全局变量
eeuiAppSetVariate(key, value) {
$A.eeuiModule()?.setVariate(key, value);
},
// 获取全局变量Expo 壳setVariate 时 RN 会 broadcast 到所有 WebView 的 __EXPO_VARIATES__
eeuiAppGetVariate(key, defaultVal = "") {
const cache = window.__EXPO_VARIATES__;
if (cache && Object.prototype.hasOwnProperty.call(cache, key)) {
return cache[key];
}
return $A.eeuiModule()?.getVariate(key, defaultVal);
},
// 设置缓存数据
eeuiAppSetCachesString(key, value, expired = 0) {
$A.eeuiModule()?.setCachesString(key, value, expired);
},
// 获取缓存数据Expo 壳:若 __EXPO_CACHES__ 已 hydrate 就同步读取,否则回落到原生桥)
eeuiAppGetCachesString(key, defaultVal = "") {
const cache = window.__EXPO_CACHES__;
if (cache && Object.prototype.hasOwnProperty.call(cache, key)) {
return cache[key];
}
return $A.eeuiModule()?.getCachesString(key, defaultVal);
},
// 是否长按内容震动仅支持android、iOS无效
eeuiAppSetHapticBackEnabled(val) {
$A.eeuiModule("webview").setHapticBackEnabled(val);
},
// 禁止长按选择仅支持android、iOS无效
eeuiAppSetDisabledUserLongClickSelect(val) {
const webview = $A.eeuiModule("webview");
$A.__disabledUserLongClickSelectTimer && clearTimeout($A.__disabledUserLongClickSelectTimer);
if (!/^\d+$/.test(val)) {
webview.setDisabledUserLongClickSelect(val);
return;
}
webview.setDisabledUserLongClickSelect(true);
$A.__disabledUserLongClickSelectTimer = setTimeout(() => {
$A.__disabledUserLongClickSelectTimer = null;
webview.setDisabledUserLongClickSelect(false);
}, val);
},
__disabledUserLongClickSelectTimer: null,
// 复制文本
eeuiAppCopyText(text) {
$A.eeuiModule()?.copyText(text)
},
// 设置是否禁止滚动
eeuiAppSetScrollDisabled(disabled) {
if (disabled) {
$A.__setScrollDisabledNum++
} else {
$A.__setScrollDisabledNum--
}
$A.eeuiModule("webview")?.setScrollEnabled($A.__setScrollDisabledNum <= 0);
},
__setScrollDisabledNum: 0,
// 设置应用程序级别的摇动撤销仅支持iOS、android无效
eeuiAppShakeToEditEnabled(enabled) {
if (enabled) {
$A.eeuiModule()?.shakeToEditOn();
} else {
$A.eeuiModule()?.shakeToEditOff();
}
},
// 获取最新一张照片
eeuiAppGetLatestPhoto(expiration = 60, timeout = 10) {
return new Promise(async (resolve, reject) => {
try {
const eeui = await $A.eeuiModule();
const timer = timeout > 0 ? setTimeout(() => {
reject({msg: "timeout"});
}, timeout * 1000) : null;
eeui.getLatestPhoto(result => {
timer && clearTimeout(timer);
if (
result.status !== 'success' ||
result.thumbnail.width < 10 || !result.thumbnail.base64 ||
result.original.width < 10 || !result.original.path
) {
return reject({msg: result.error || "no photo"});
}
if (expiration > 0 && (result.created + expiration) < $A.dayjs().unix()) {
return reject({msg: "photo expired"});
}
if ($A.__latestPhotoCreated && $A.__latestPhotoCreated === result.created) {
return reject({msg: "photo expired"});
}
$A.__latestPhotoCreated = result.created;
resolve(result);
});
} catch (e) {
reject(e);
}
})
},
__latestPhotoCreated: null,
// 上传照片(通过 eeuiAppGetLatestPhoto 获取到的pathparams 参数:{url,data,headers,path,fieldName}
eeuiAppUploadPhoto(params, timeout = 30) {
return new Promise(async (resolve, reject) => {
try {
const eeui = await $A.eeuiModulePromise();
const timer = timeout > 0 ? setTimeout(() => {
reject({msg: "timeout"});
}, timeout * 1000) : null;
if (!$A.isJson(params)) {
return reject({msg: "params error"});
}
let onReady = null;
if (typeof params.onReady !== "undefined") {
onReady = params.onReady;
delete params.onReady;
}
eeui.uploadPhoto(params, result => {
if (result.status === 'ready') {
typeof onReady === "function" && onReady(result.id)
return
}
timer && clearTimeout(timer);
if (result.status !== 'success') {
return reject({msg: result.error || "upload failed"});
}
if (result.data.ret !== 1) {
return reject({msg: result.data.msg || "upload failed"});
}
resolve(result.data.data);
});
} catch (e) {
reject(e);
}
})
},
// 取消上传照片
eeuiAppCancelUploadPhoto(id) {
return new Promise(async (resolve, reject) => {
try {
const eeui = await $A.eeuiModulePromise();
eeui.cancelUploadPhoto(id, result => {
if (result.status !== 'success') {
return reject({msg: result.error || "cancel failed"});
}
resolve(result);
});
} catch (e) {
reject(e);
}
})
},
// 获取导航栏和状态栏高度
eeuiAppGetSafeAreaInsets() {
return new Promise(async (resolve, reject) => {
try {
const eeui = await $A.eeuiModulePromise();
eeui.getSafeAreaInsets(result => {
if (result.status !== 'success') {
return reject({msg: result.error || "get failed"});
}
resolve(result);
});
} catch (e) {
reject(e);
}
})
},
// 获取当前语言
eeuiAppConvertLanguage() {
const specialMappings = {
"zh": "zh-Hans",
"zh-CHT": "zh-Hant"
};
return specialMappings[languageName] || languageName;
},
// 获取设备信息
eeuiAppGetDeviceInfo() {
return new Promise(async (resolve, reject) => {
try {
const eeui = await $A.eeuiModulePromise();
eeui.getDeviceInfo(result => {
if (result.status !== 'success') {
return reject({msg: result.error || "get failed"});
}
resolve(result);
});
} catch (e) {
reject(e);
}
})
},
// 判断是否窗口化
eeuiAppIsWindowed() {
return new Promise(async resolve => {
try {
const eeui = await $A.eeuiModulePromise();
resolve(eeui.isFullscreen() === false || eeui.isFullscreen() === 0);
} catch (e) {
resolve(false);
}
})
}
});
window.$A = $;
})(window);