kuaifan f479047fc9 feat(mobile): 主程序前端 nativeApp 桥接契约改造 + /share 接收页 + 子模块切到 Expo 工程
新移动端从 EEUI(weex) 切到 Expo(react-native),UA 标识从 'eeui' 改为 'DooTaskApp',
桥接底层从全局 requireModuleJs 切到 window.__nativeBridge.call(method,args) Promise RPC。
约 52 个文件批量改名(256 处引用):

- 平台识别:$A.isEEUIApp/Vue.prototype.$isEEUIApp → isMobileApp / $isMobileApp
  app.js 顶部 UA 正则 /eeui/i → /DooTaskApp/i
  app.js requireModuleJs 等待逻辑替换为 window.__nativeBridge 就绪判定
- 桥接 API:$A.eeuiAppXXX → $A.nativeAppXXX
- functions/eeui.js 删除,重写为 functions/native-app.js
  底层走 window.__nativeBridge.call;nativeAppSendMessage 把旧 sendMessage 的
  各 action 拆解为独立 method 调用(updateTheme/windowSize/setBadge/vibrate/
  callTel/picturePreview/videoPreview/startMeeting/updateMeetingInfo 等)
- types/dootask-globals.d.ts:方法签名同步改名
- store/actions.js:清理 saveUserInfoBase 内旧的
  userChatList/userUploadUrl sendMessage(二期由 /share 接收页直接调接口替代)

二期 /share 接收页(跨 App 分享):
- pages/share.vue(新增):原生 Share Extension / Android Intent 通过
  dootask://share?token=... 拉起 App 后跳本页;
  调 nativeAppGetSharedPayload 取分享元数据;
  复用 api/users/share/list 渲染会话列表;
  用户选会话 → POST api/dialog/msg/sendfiles 上传 + sendtext 发文本/URL;
  完成后 nativeAppClearSharedPayload 清理 + 跳目标会话
- routes.js 注册 /share 路由
- language/original-web.txt 加 share.vue 用到的文案

构建链路(electron/build.js):
- appbuild app 子命令:前端产物从复制到 resources/mobile/src/public + EEUI docker 构建
  改为同步到 resources/mobile/assets/web(由 plugins/withWebAssets 注入原生 bundle)
- android-upload apk 路径:从 platforms/android/eeuiApp/... 改为
  android/app/build/outputs/apk/release(Expo prebuild 标准布局)

子模块 resources/mobile 指针更新到新 Expo 工程提交(feat/mobile-expo-rewrite 分支)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 06:55:52 +00:00

195 lines
9.5 KiB
JavaScript
Executable File
Vendored
Raw Permalink 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.

/**
* Native App 桥接(替代旧 functions/eeui.js
*
* 底层从 weex 的 requireModuleJs 切换为新 RN 桥接 window.__nativeBridgesrc/bridge/injected.ts 注入)。
* 方法名规则:$A.nativeAppXxx(args) → __nativeBridge.call('xxx', args),其中 xxx 为首字母小写。
*
* 契约见 resources/mobile/src/bridge/CONTRACT.md。
*/
import {languageName} from "../language";
(function (window) {
const $ = window.$A;
/** 通用调用入口:返回 Promise桥接未就绪则 resolve undefined避免抛错炸主流程 */
function call(method, args) {
const bridge = window.__nativeBridge;
if (!bridge || typeof bridge.call !== "function") {
return Promise.resolve(undefined);
}
return bridge.call(method, args === undefined ? null : args);
}
/** 同步调用包装:调用方需要立刻拿到值的场景(仅适用于在 native 端有缓存的 method
* 注意:所有桥接调用本质是异步;同步方法目前用兜底默认值返回。 */
function syncDefault(_method, defaultValue) { return defaultValue; }
$.extend({
// === 设备/系统 ===
nativeAppVersion() { return syncDefault('getAppVersion', undefined); },
nativeAppVersionAsync() { return call('getAppVersion'); },
nativeAppLocalVersion() { return syncDefault('getAppLocalVersion', 0); },
nativeAppLocalVersionAsync() { return call('getAppLocalVersion'); },
nativeAppGetPageInfo() { return {}; },
nativeAppGetThemeName() { return syncDefault('getThemeName', undefined); },
nativeAppKeyboardStatus() { return syncDefault('keyboardStatus', undefined); },
nativeAppRewriteUrl(val) { return val; },
// === UI 容器控制 ===
nativeAppAlert(object, callback) {
if (typeof callback !== "function") callback = _ => {};
call('alert', object).then(callback).catch(callback);
},
nativeAppToast(object) { call('toast', typeof object === 'string' ? { message: object } : object); },
nativeAppKeyboardHide() { call('keyboardHide'); },
nativeAppKeepScreenOn() { call('keepScreenOn'); },
nativeAppKeepScreenOff() { call('keepScreenOff'); },
nativeAppSetPageBackPressed(object, callback) {
if (typeof callback !== "function") callback = _ => {};
// 旧契约:原生回调由 setPageBackPressed 触发;新契约用全局事件 __onBackPressed
window.__onBackPressed = callback;
call('setPageBackPressed', object);
},
nativeAppGoDesktop() { call('goDesktop'); },
nativeAppSetHapticBackEnabled(val) { call('setHapticBackEnabled', !!val); },
nativeAppSetDisabledUserLongClickSelect(val) {
$.__disabledUserLongClickSelectTimer && clearTimeout($.__disabledUserLongClickSelectTimer);
if (!/^\d+$/.test(val)) {
call('setScrollDisabled', !!val);
return;
}
call('setScrollDisabled', true);
$.__disabledUserLongClickSelectTimer = setTimeout(() => {
$.__disabledUserLongClickSelectTimer = null;
call('setScrollDisabled', false);
}, parseInt(val));
},
__disabledUserLongClickSelectTimer: null,
nativeAppSetScrollDisabled(disabled) {
if (disabled) {
$.__setScrollDisabledNum++
} else {
$.__setScrollDisabledNum--
}
call('setScrollDisabled', $.__setScrollDisabledNum > 0);
},
__setScrollDisabledNum: 0,
nativeAppShakeToEditEnabled(_enabled) { /* iOS 摇动撤销在 RN 由系统默认支持no-op */ },
// === 存储 ===
nativeAppSetVariate(key, value) { call('setVariate', { key, value }); },
nativeAppGetVariate(_key, defaultVal = "") {
// 异步返回;同步签名兼容兜底(不推荐使用)
return defaultVal;
},
nativeAppGetVariateAsync(key, defaultVal = "") {
return call('getVariate', { key, defaultVal });
},
nativeAppSetCachesString(key, value, expired = 0) { call('setCache', { key, value, expired }); },
nativeAppGetCachesString(_key, defaultVal = "") { return defaultVal; },
nativeAppGetCachesStringAsync(key, defaultVal = "") {
return call('getCache', { key, defaultVal });
},
// === 媒体 ===
nativeAppScan(callback) {
call('scan').then((text) => { typeof callback === 'function' && callback(text); });
},
nativeAppCopyText(text) { call('copyText', text); },
nativeAppGetLatestPhoto(expiration = 60, _timeout = 10) {
return call('getLatestPhoto', { expiration }).then((res) => {
// 旧契约外形:{thumbnail:{base64,width,height}, original:{path,width,height}}
// 新最小实现只返回 {path,width,height,created},包装为兼容形态。
if (!res || !res.path) return Promise.reject({ msg: 'no photo' });
return {
thumbnail: { base64: '', width: res.width, height: res.height },
original: { path: res.path, width: res.width, height: res.height },
created: res.created,
};
});
},
nativeAppUploadPhoto(params, _timeout = 30) {
const onReady = params && params.onReady;
if (typeof onReady === 'function') delete params.onReady;
return call('uploadPhoto', params).then((data) => {
if (typeof onReady === 'function') onReady();
return data;
});
},
nativeAppCancelUploadPhoto(id) { return call('cancelUploadPhoto', { id }); },
// === 交互/外链 ===
nativeAppOpenPage(object, callback) {
if (typeof callback !== "function") callback = _ => {};
if (typeof object.callback === "function") {
callback = object.callback;
delete object.callback
}
call('openPage', object).then(callback).catch(callback);
},
nativeAppOpenWeb(url) { call('openWeb', url); },
nativeAppSetUrl(url) { call('setUrl', url); },
nativeAppGetWebviewSnapshot(callback) { typeof callback === 'function' && callback({status: 'noop'}); },
nativeAppShowWebviewSnapshot() { /* 新移动端不再用 weex 快照机制 */ },
nativeAppHideWebviewSnapshot() { /* 同上 */ },
nativeAppCheckUpdate() { call('checkUpdate'); },
// === 信息(异步)===
nativeAppGetDeviceInfo() {
return call('getDeviceInfo').then((info) => {
if (!info) return Promise.reject({ msg: 'no device info' });
return info;
});
},
nativeAppGetSafeAreaInsets() {
return call('getSafeAreaInsets').then((insets) => {
if (!insets) return Promise.reject({ msg: 'no safe area' });
return { status: 'success', top: insets.top, bottom: insets.bottom, data: insets };
});
},
nativeAppIsWindowed() { return call('isWindowed').then((v) => !!v); },
// === 平台无关:纯前端语言映射(不走桥接) ===
nativeAppConvertLanguage() {
const specialMappings = {"zh": "zh-Hans", "zh-CHT": "zh-Hant"};
return specialMappings[languageName] || languageName;
},
// === sendMessage拆解为独立 method ===
// 旧 $A.nativeAppSendMessage({action:'xxx', ...}) 全部路由到对应桥接 method。
nativeAppSendMessage(object) {
if (!object || typeof object !== 'object') return;
const action = object.action;
const rest = Object.assign({}, object); delete rest.action;
switch (action) {
case 'updateTheme': return call('updateTheme', rest);
case 'windowSize': return call('windowSize', rest);
case 'setBadgeNum':
case 'setBdageNotify': return call('setBadge', { count: parseInt(rest.bdage) || 0 });
case 'setVibrate': return call('vibrate', { time: parseInt(rest.time) || 0 });
case 'callTel': return call('callTel', { tel: rest.tel });
case 'picturePreview': return call('picturePreview', rest);
case 'videoPreview': return call('videoPreview', rest);
case 'clearAllNotify': return call('clearAllNotify');
case 'gotoSetting': return call('gotoNotificationSetting');
case 'getNotificationPermission': return call('getNotificationPermission');
case 'startMeeting': return call('startMeeting', rest);
case 'updateMeetingInfo': return call('updateMeetingInfo', rest);
// 推送注册:新链路 — 不再走 sendMessage外层逻辑改用 registerPush
// 旧 action 静默忽略以兼容尚未迁移的调用点
case 'initApp':
case 'setUmengAlias':
case 'delUmengAlias':
return;
default:
// 未识别 action调试时打日志避免静默失败
if (window.systemInfo && window.systemInfo.debug === 'yes') {
console.warn('[nativeApp] unhandled sendMessage action:', action, rest);
}
}
},
});
window.$A = $;
})(window);