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>
This commit is contained in:
kuaifan 2026-06-17 06:55:52 +00:00
parent 9151939ba5
commit f479047fc9
59 changed files with 772 additions and 650 deletions

55
electron/build.js vendored
View File

@ -401,48 +401,15 @@ async function startBuild(data) {
fs.writeFileSync(indexFile, indexString, 'utf8');
//
if (data.id === 'app') {
const eeuiDir = path.resolve(__dirname, "../resources/mobile");
const publicDir = path.resolve(__dirname, "../resources/mobile/src/public");
const containerName = `dootask-eeui-${Date.now()}-${process.pid}`;
fse.removeSync(publicDir)
fse.copySync(electronDir, publicDir)
if (argv[3] === "publish") {
// Android config
const gradleFile = path.resolve(eeuiDir, "platforms/android/eeuiApp/local.properties")
let gradleResult = fs.existsSync(gradleFile) ? fs.readFileSync(gradleFile, 'utf8') : "";
gradleResult = gradleResult.replace(/(versionCode|versionName)\s*=\s*(.+?)(\n|$)/g, '')
gradleResult += `versionCode = ${config.codeVerson}\nversionName = ${config.version}\n`
fs.writeFileSync(gradleFile, gradleResult, 'utf8')
// iOS config
const xcconfigFile = path.resolve(eeuiDir, "platforms/ios/eeuiApp/Config/Version.xcconfig")
let xcconfigResult = fs.existsSync(xcconfigFile) ? fs.readFileSync(xcconfigFile, 'utf8') : "";
xcconfigResult = xcconfigResult.replace(/(VERSION_CODE|VERSION_NAME)\s*=\s*(.+?)(\n|$)/g, '')
xcconfigResult += `VERSION_CODE = ${config.codeVerson}\nVERSION_NAME = ${config.version}\n`
fs.writeFileSync(xcconfigFile, xcconfigResult, 'utf8')
}
if (['build', 'publish'].includes(argv[3])) {
child_process.execSync(
`docker run -d --name ${containerName} -v ${shellQuote(eeuiDir)}:/work -w /work kuaifan/eeui-cli:0.0.1 sleep infinity`,
{stdio: "ignore", cwd: "resources/mobile"}
);
try {
if (!fs.existsSync(path.resolve(eeuiDir, "node_modules"))) {
child_process.execSync(`docker exec ${containerName} npm install`, {stdio: "inherit", cwd: "resources/mobile"});
}
child_process.execSync(`docker exec ${containerName} node /work/scripts/patch-eeui-build.js`, {stdio: "inherit", cwd: "resources/mobile"});
child_process.execSync(`docker exec ${containerName} eeui build --simple`, {stdio: "inherit", cwd: "resources/mobile"});
} finally {
child_process.execSync(`docker rm -f ${containerName}`, {stdio: "ignore", cwd: "resources/mobile"});
}
} else {
[
path.resolve(publicDir, "../../platforms/ios/eeuiApp/bundlejs/eeui/public"),
path.resolve(publicDir, "../../platforms/android/eeuiApp/app/src/main/assets/eeui/public"),
].some(dir => {
fse.removeSync(dir)
fse.copySync(electronDir, dir)
})
}
// 新 Expo 移动端:将前端构建产物作为离线包同步到 resources/mobile/assets/web
// 由 config plugin(withWebAssets) 在 prebuild 时注入原生 bundle运行时本地静态服务 serve。
const mobileDir = path.resolve(__dirname, "../resources/mobile");
const webDir = path.resolve(mobileDir, "assets/web");
fse.removeSync(webDir)
fse.copySync(electronDir, webDir)
console.log(`移动端离线包已同步: ${webDir}`);
// 版本号由 resources/mobile/app.config.ts 从根 package.json 动态读取,无需改原生工程;
// App 原生打包在 resources/mobile 下执行 expo prebuild + eas build见 WS7
return;
}
const output = `dist/${data.id.replace(/\./g, '-')}/${platform}`
@ -560,7 +527,9 @@ if (["dev"].includes(argv[2])) {
process.exit(1)
}
const client = r2.createR2Client()
const releaseDir = path.resolve(__dirname, "../resources/mobile/platforms/android/eeuiApp/app/build/outputs/apk/release");
// 新 Expo 移动端apk 由 EAS Build 输出(或本地 expo prebuild + gradle assembleRelease
// EAS 流水线产物路径示例android/app/build/outputs/apk/release 或 EAS 远端构建直接上传。
const releaseDir = path.resolve(__dirname, "../resources/mobile/android/app/build/outputs/apk/release");
if (!fs.existsSync(releaseDir)) {
console.error("发布文件未找到")
process.exit(1)

View File

@ -2469,3 +2469,10 @@ AI任务分析
操作引导启动失败
最多只能添加(*)个
该标签已存在
发送给
搜索会话
暂无会话
缺少分享凭据
分享内容不存在或已过期
加载会话列表失败
正在发送

View File

@ -217,11 +217,11 @@ export default {
handler() {
this.$store.dispatch("websocketConnection");
//
if (this.$isEEUIApp) {
if (this.$isMobileApp) {
this.umengAliasTimer && clearTimeout(this.umengAliasTimer)
if (this.userId > 0) {
// APP
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'initApp',
apiUrl: $A.apiUrl(''),
userid: this.userId,
@ -231,7 +231,7 @@ export default {
});
//
$A.eeuiAppGetDeviceInfo().then(async info => {
$A.nativeAppGetDeviceInfo().then(async info => {
let deviceName = info.deviceName || info.modelName
if (info.systemName === 'Android') {
if ($A.strExists(info.modelName, info.brand)) {
@ -254,14 +254,14 @@ export default {
//
this.umengAliasTimer = setTimeout(_ => {
this.umengAliasTimer = null;
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'setUmengAlias',
url: $A.apiUrl('users/umeng/alias')
});
}, 6000)
} else {
//
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'delUmengAlias',
url: $A.apiUrl('users/umeng/alias')
});
@ -385,7 +385,7 @@ export default {
},
onRouterViewMounted() {
document.documentElement.setAttribute("data-platform", $A.isElectron ? "desktop" : $A.isEEUIApp ? "app" : "web")
document.documentElement.setAttribute("data-platform", $A.isElectron ? "desktop" : $A.isMobileApp ? "app" : "web")
},
/**
@ -649,12 +649,12 @@ export default {
},
eeuiEvents() {
if (!this.$isEEUIApp) {
if (!this.$isMobileApp) {
return;
}
//
setTimeout(() => {
this.appActivated && $A.eeuiAppHideWebviewSnapshot()
this.appActivated && $A.nativeAppHideWebviewSnapshot()
}, 500)
// APP
window.__onAppActive = async () => {
@ -667,12 +667,12 @@ export default {
this.autoTheme()
$A.updateTimezone()
$A.eeuiAppHideWebviewSnapshot()
$A.nativeAppHideWebviewSnapshot()
this.$store.dispatch("safeAreaInsets")
const nowYmd = $A.daytz().format('YYYY-MM-DD')
if (this.lastCheckUpgradeYmd != nowYmd) {
this.lastCheckUpgradeYmd = nowYmd
$A.eeuiAppCheckUpdate();
$A.nativeAppCheckUpdate();
}
}
// APP
@ -683,12 +683,12 @@ export default {
// APP
return;
}
$A.eeuiAppGetWebviewSnapshot(ok => {
$A.nativeAppGetWebviewSnapshot(ok => {
if (!ok || this.appActivated) {
// APP
return;
}
$A.eeuiAppShowWebviewSnapshot()
$A.nativeAppShowWebviewSnapshot()
});
}, 500);
}
@ -714,7 +714,7 @@ export default {
return;
} else if (urlType === 1) {
// 使
$A.eeuiAppOpenWeb(url);
$A.nativeAppOpenWeb(url);
return;
}
// App
@ -741,7 +741,7 @@ export default {
tourist_id: event.uuid,
}
}).then(({data}) => {
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'updateMeetingInfo',
infos: {
uuid: event.uuid,
@ -790,7 +790,7 @@ export default {
}
this.$store.state.keyboardShow = event.keyboardType === 'show';
this.$store.state.keyboardHeight = event.keyboardHeight;
$A.eeuiAppShakeToEditEnabled(this.$store.state.keyboardShow)
$A.nativeAppShakeToEditEnabled(this.$store.state.keyboardShow)
}
//
window.__onNotificationPermissionStatus = (ret) => {
@ -801,22 +801,22 @@ export default {
this.goForward({ path: (path || '').indexOf('/') !==0 ? "/" + path : path });
}
//
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'windowSize',
width: this.windowWidth,
height: this.windowHeight,
});
//
$A.eeuiAppSetHapticBackEnabled(false)
$A.nativeAppSetHapticBackEnabled(false)
//
$A.eeuiAppSetCachesString("languageWebBack", this.$L("后退"))
$A.eeuiAppSetCachesString("languageWebForward", this.$L("前进"))
$A.eeuiAppSetCachesString("languageWebBrowser", this.$L("浏览器打开"))
$A.eeuiAppSetCachesString("languageWebRefresh", this.$L("刷新"))
$A.eeuiAppSetCachesString("updateDefaultTitle", this.$L("发现新版本"))
$A.eeuiAppSetCachesString("updateDefaultContent", this.$L("暂无更新介绍!"))
$A.eeuiAppSetCachesString("updateDefaultCancelText", this.$L("以后再说"))
$A.eeuiAppSetCachesString("updateDefaultUpdateText", this.$L("立即更新"))
$A.nativeAppSetCachesString("languageWebBack", this.$L("后退"))
$A.nativeAppSetCachesString("languageWebForward", this.$L("前进"))
$A.nativeAppSetCachesString("languageWebBrowser", this.$L("浏览器打开"))
$A.nativeAppSetCachesString("languageWebRefresh", this.$L("刷新"))
$A.nativeAppSetCachesString("updateDefaultTitle", this.$L("发现新版本"))
$A.nativeAppSetCachesString("updateDefaultContent", this.$L("暂无更新介绍!"))
$A.nativeAppSetCachesString("updateDefaultCancelText", this.$L("以后再说"))
$A.nativeAppSetCachesString("updateDefaultUpdateText", this.$L("立即更新"))
},
otherEvents() {

View File

@ -1,6 +1,7 @@
const isElectron = !!(window && window.process && window.process.type && window.electron);
const isEEUIApp = window && window.navigator && /eeui/i.test(window.navigator.userAgent);
const isSoftware = isElectron || isEEUIApp;
// 新移动端在 UA 追加 DooTaskApp/<codeVersion>resources/mobile/src/webview/WebViewShell.tsx
const isMobileApp = window && window.navigator && /DooTaskApp/i.test(window.navigator.userAgent);
const isSoftware = isElectron || isMobileApp;
document.getElementById("app")?.setAttribute("data-preload", "false");
@ -8,7 +9,7 @@ import {languageName, switchLanguage as $L} from "./language";
import {isLocalHost} from "./components/Replace/utils";
import './functions/common'
import './functions/eeui'
import './functions/native-app'
import './functions/web'
import Vue from 'vue'
@ -188,8 +189,8 @@ Vue.prototype.copyText = function (obj) {
error: "复制失败"
}
}
if ($A.isEEUIApp) {
$A.eeuiAppCopyText(obj.text)
if ($A.isMobileApp) {
$A.nativeAppCopyText(obj.text)
obj.success && $A.messageSuccess(obj.success)
return
}
@ -207,7 +208,7 @@ $A.Electron = null;
$A.Platform = "web";
$A.isMainElectron = false;
$A.isSubElectron = false;
$A.isEEUIApp = isEEUIApp;
$A.isMobileApp = isMobileApp;
$A.isElectron = isElectron;
$A.isSoftware = isSoftware;
$A.openLog = false;
@ -216,7 +217,7 @@ if (isElectron) {
$A.Platform = /macintosh|mac os x/i.test(navigator.userAgent) ? "mac" : "win";
$A.isMainElectron = /\s+MainTaskWindow\//.test(window.navigator.userAgent);
$A.isSubElectron = /\s+SubTaskWindow\//.test(window.navigator.userAgent);
} else if (isEEUIApp) {
} else if (isMobileApp) {
$A.Platform = /(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent) ? "ios" : "android";
}
@ -287,7 +288,7 @@ Vue.prototype.$Electron = $A.Electron;
Vue.prototype.$Platform = $A.Platform;
Vue.prototype.$isMainElectron = $A.isMainElectron;
Vue.prototype.$isSubElectron = $A.isSubElectron;
Vue.prototype.$isEEUIApp = $A.isEEUIApp;
Vue.prototype.$isMobileApp = $A.isMobileApp;
Vue.prototype.$isSoftware = $A.isSoftware;
Vue.config.productionTip = false;
@ -324,15 +325,16 @@ const $init = async () => {
const $preload = async () => {
document.getElementById("app")?.setAttribute("data-preload", "true")
if ($A.isEEUIApp) {
if ($A.isMobileApp) {
// 等待新 RN 桥接传输层window.__nativeBridge就绪注入时机为 webview 加载内容前
const requireTime = new Date().getTime();
while (typeof requireModuleJs !== "function") {
while (!window.__nativeBridge) {
await new Promise(resolve => setTimeout(resolve, 200));
if (new Date().getTime() - requireTime > 15 * 1000) {
break
}
}
if (typeof requireModuleJs !== "function") {
if (!window.__nativeBridge) {
const errorTip = $A.L("加载失败,请重启软件")
const errorView = document.querySelector(".app-view-loading")
if (errorView) {
@ -342,8 +344,8 @@ const $preload = async () => {
}
return
}
const pageInfo = $A.eeuiAppGetPageInfo() || {};
store.state.isFirstPage = pageInfo.pageName === 'firstPage'
// 新移动端单 WebView 套壳永远是首屏isFirstPage 恒 true旧 EEUI 是多 page 容器才需要判定)
store.state.isFirstPage = true
await store.dispatch("safeAreaInsets")
}

View File

@ -4,7 +4,7 @@
:placement="placement"
:effect="tooltipTheme"
:delay="delay"
:disabled="$isEEUIApp || windowTouch || !showTooltip || disabled"
:disabled="$isMobileApp || windowTouch || !showTooltip || disabled"
:max-width="tooltipMaxWidth"
transfer>
<span ref="content" @mouseenter="handleTooltipIn" class="common-auto-tip" @click="onClick">

View File

@ -1,5 +1,5 @@
<template>
<ETooltip v-if="visible" :disabled="$isEEUIApp || windowTouch || content == ''" :content="content">
<ETooltip v-if="visible" :disabled="$isMobileApp || windowTouch || content == ''" :content="content">
<svg v-if="type === 'svg'" viewBox="25 25 50 50" class="common-loading">
<circle cx="50" cy="50" r="20" fill="none" stroke-width="5" stroke-miterlimit="10" class="common-path"></circle>
</svg>

View File

@ -257,7 +257,7 @@ export default {
systemInfo: window.systemInfo,
windowType: this.windowType,
isEEUIApp: $A.isEEUIApp,
isMobileApp: $A.isMobileApp,
isElectron: $A.isElectron,
isMainElectron: $A.isMainElectron,
isSubElectron: $A.isSubElectron,
@ -515,7 +515,7 @@ export default {
width: mergedConfig.width,
height: mergedConfig.height,
});
} else if (this.$isEEUIApp) {
} else if (this.$isMobileApp) {
await this.$store.dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: ' ',
@ -541,7 +541,7 @@ export default {
path: config.url,
title: config.title || ' ',
});
} else if (this.$isEEUIApp) {
} else if (this.$isMobileApp) {
await this.$store.dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: ' ',

View File

@ -175,14 +175,14 @@ export default {
},
appAndroidEvents() {
if (this.$isEEUIApp && $A.isAndroid()) {
$A.eeuiAppSetPageBackPressed({
if (this.$isMobileApp && $A.isAndroid()) {
$A.nativeAppSetPageBackPressed({
pageName: 'firstPage',
}, _ => {
if (this.canBack()) {
this.onBack();
} else {
$A.eeuiAppGoDesktop()
$A.nativeAppGoDesktop()
}
});
}

View File

@ -69,7 +69,7 @@ export default {
if (this.duration > 0) {
this.timer = setTimeout(this.close, this.duration)
}
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'setVibrate',
});
},

View File

@ -198,7 +198,7 @@ export default {
if (this.windowActive) {
return
}
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'setBdageNotify',
bdage: this.unreadAndOverdue,
});

View File

@ -32,7 +32,7 @@ export default {
},
previewImageList(l) {
if (l.length > 0) {
if ($A.isEEUIApp || $A.isElectron) {
if ($A.isMobileApp || $A.isElectron) {
let position = Math.min(Math.max(this.$store.state.previewImageIndex, 0), this.$store.state.previewImageList.length - 1)
let paths = l.map(item => {
if ($A.isJson(item)) {
@ -74,9 +74,9 @@ export default {
},
methods: {
videoPreview(path) {
if ($A.isEEUIApp) {
$A.eeuiAppSendMessage({
language: $A.eeuiAppConvertLanguage(),
if ($A.isMobileApp) {
$A.nativeAppSendMessage({
language: $A.nativeAppConvertLanguage(),
action: 'videoPreview',
path
});
@ -89,9 +89,9 @@ export default {
}
},
imagePreview(index, paths) {
if ($A.isEEUIApp) {
$A.eeuiAppSendMessage({
language: $A.eeuiAppConvertLanguage(),
if ($A.isMobileApp) {
$A.nativeAppSendMessage({
language: $A.nativeAppConvertLanguage(),
action: 'picturePreview',
position: index,
paths

View File

@ -18,7 +18,7 @@ const convertLocalResourcePath = (() => {
if (initialized) return
// 设置应用前缀URL
if ($A.isEEUIApp || $A.isElectron) {
if ($A.isMobileApp || $A.isElectron) {
appPreUrl = window.location.origin + "/"
}

View File

@ -94,14 +94,14 @@ export default {
},
showDown() {
if (this.$Electron || this.$isEEUIApp || this.windowTouch) {
if (this.$Electron || this.$isMobileApp || this.windowTouch) {
return false
}
return this.routeName === 'login'
},
showPrivacy() {
return $A.isDooServer() && this.$isEEUIApp && ['login'].includes(this.routeName)
return $A.isDooServer() && this.$isMobileApp && ['login'].includes(this.routeName)
}
},

View File

@ -14,7 +14,7 @@
<Loading v-if="loadIng > 0"/>
<Icon v-else type="ios-search" />
</div>
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.eeuiAppKeyboardHide">
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.nativeAppKeyboardHide">
<Input type="search" ref="searchKey" v-model="searchKey" :placeholder="$L('请输入关键字')"/>
</Form>
<div v-if="aiSearchAvailable" class="search-ai" @click="toggleAiSearch">
@ -142,7 +142,7 @@ export default {
},
showModal(v) {
$A.eeuiAppSetScrollDisabled(v)
$A.nativeAppSetScrollDisabled(v)
}
},
@ -262,7 +262,7 @@ export default {
},
onTouchstart() {
$A.eeuiAppKeyboardHide();
$A.nativeAppKeyboardHide();
},
onTag(type, e) {

View File

@ -12,7 +12,7 @@
v-if="item.hidden !== true"
placement="top"
:key="key"
:disabled="$isEEUIApp || windowTouch || !item.title"
:disabled="$isMobileApp || windowTouch || !item.title"
:content="item.title"
:enterable="false"
:open-delay="600">

View File

@ -1,7 +1,7 @@
<template>
<ETooltip
:open-delay="openDelay"
:disabled="$isEEUIApp || windowTouch || tooltipDisabled || isBot"
:disabled="$isMobileApp || windowTouch || tooltipDisabled || isBot"
:placement="tooltipPlacement">
<div v-if="user" slot="content" class="common-avatar-transfer">
<slot/>

View File

@ -63,7 +63,7 @@
<Loading v-if="loadIng > 0"/>
<Icon v-else type="ios-search"/>
</div>
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.eeuiAppKeyboardHide">
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.nativeAppKeyboardHide">
<Input
type="search"
v-model="searchKey"
@ -377,7 +377,7 @@ export default {
}
this.$emit("on-show-change", v)
//
$A.eeuiAppSetScrollDisabled(v && this.windowPortrait)
$A.nativeAppSetScrollDisabled(v && this.windowPortrait)
},
searchKey() {

View File

@ -782,7 +782,7 @@ const timezone = require("dayjs/plugin/timezone");
* @returns {string}
*/
reloadUrl() {
if ($A.isEEUIApp && $A.isAndroid()) {
if ($A.isMobileApp && $A.isAndroid()) {
let url = window.location.href;
let key = '_='
let reg = new RegExp(key + '\\d+');
@ -805,7 +805,7 @@ const timezone = require("dayjs/plugin/timezone");
}
}
}
$A.eeuiAppSetUrl(url);
$A.nativeAppSetUrl(url);
} else {
window.location.reload();
}

View File

@ -1,381 +0,0 @@
/**
* 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版本号
eeuiAppVersion() {
return $A.eeuiModule()?.getVersion();
},
// 获取本地软件版本号
eeuiAppLocalVersion() {
return $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);
},
// 获取页面信息
eeuiAppGetPageInfo(pageName) {
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|dark
eeuiAppGetThemeName() {
return $A.eeuiModule()?.getThemeName();
},
// 判断软键盘是否可见
eeuiAppKeyboardStatus() {
return $A.eeuiModule()?.keyboardStatus();
},
// 设置全局变量
eeuiAppSetVariate(key, value) {
$A.eeuiModule()?.setVariate(key, value);
},
// 获取全局变量
eeuiAppGetVariate(key, defaultVal = "") {
return $A.eeuiModule()?.getVariate(key, defaultVal);
},
// 设置缓存数据
eeuiAppSetCachesString(key, value, expired = 0) {
$A.eeuiModule()?.setCachesString(key, value, expired);
},
// 获取缓存数据
eeuiAppGetCachesString(key, defaultVal = "") {
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);

194
resources/assets/js/functions/native-app.js vendored Executable file
View File

@ -0,0 +1,194 @@
/**
* 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);

View File

@ -881,7 +881,7 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
config.onCancel();
}
};
$A.eeuiAppKeyboardHide()
$A.nativeAppKeyboardHide()
$A.Modal.confirm({
render: (h) => {
return h('div', [
@ -958,7 +958,7 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
})
}
}
$A.eeuiAppKeyboardHide()
$A.nativeAppKeyboardHide()
$A.Modal.confirm($A.modalConfig(config));
},
@ -970,7 +970,7 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
setTimeout(() => { $A.modalSuccess(config) }, millisecond);
return;
}
$A.eeuiAppKeyboardHide()
$A.nativeAppKeyboardHide()
$A.Modal.success($A.modalConfig(config));
},
@ -982,7 +982,7 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
setTimeout(() => { $A.modalInfo(config) }, millisecond);
return;
}
$A.eeuiAppKeyboardHide()
$A.nativeAppKeyboardHide()
$A.Modal.info($A.modalConfig(config));
},
@ -997,7 +997,7 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
setTimeout(() => { $A.modalWarning(config) }, millisecond);
return;
}
$A.eeuiAppKeyboardHide()
$A.nativeAppKeyboardHide()
$A.Modal.warning($A.modalConfig(config));
},
@ -1012,7 +1012,7 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
setTimeout(() => { $A.modalError(config) }, millisecond);
return;
}
$A.eeuiAppKeyboardHide()
$A.nativeAppKeyboardHide()
$A.Modal.error($A.modalConfig(config));
},
@ -1020,7 +1020,7 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
if (msg === false) {
return;
}
$A.eeuiAppKeyboardHide()
$A.nativeAppKeyboardHide()
alert($A.L(msg));
},
@ -1273,8 +1273,8 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
autoDarkMode() {
let darkScheme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
if ($A.isEEUIApp) {
darkScheme = $A.eeuiAppGetThemeName() === "dark"
if ($A.isMobileApp) {
darkScheme = $A.nativeAppGetThemeName() === "dark"
}
if (darkScheme) {
this.enableDarkMode()

View File

@ -6,7 +6,7 @@
<div class="login-box">
<div class="login-mode-switch">
<div class="login-mode-switch-box">
<ETooltip :disabled="$isEEUIApp || windowTouch" :content="$L(loginMode=='qrcode' ? '帐号登录' : '扫码登录')" placement="left">
<ETooltip :disabled="$isMobileApp || windowTouch" :content="$L(loginMode=='qrcode' ? '帐号登录' : '扫码登录')" placement="left">
<span class="login-mode-switch-icon no-dark-content" @click="switchLoginMode">
<svg v-if="loginMode=='qrcode'" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" data-icon="PcOutlined"><path d="M23 16a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h18a2 2 0 0 1 2 2v12ZM21 4H3v9h18V4ZM3 15v1h18v-1H3Zm3 6a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H7a1 1 0 0 1-1-1Z" fill="currentColor"></path></svg>
<svg v-else viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" data-icon="QrOutlined"><path d="M6.5 7.5a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1Z" fill="currentColor"></path><path d="M4.5 2.5c-1.1 0-2 .9-2 2v7c0 1.1.9 2 2 2h7c1.1 0 2-.9 2-2v-7c0-1.1-.9-2-2-2h-7Zm0 2h7v7h-7v-7ZM11 16a1 1 0 1 1 2 0 1 1 0 0 1-2 0Zm0 3.5a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1Zm4-7.5a1 1 0 1 1 2 0 1 1 0 0 1-2 0Zm3.5 0a1 1 0 0 1 1-1h1a1 1 0 1 1 0 2h-1a1 1 0 0 1-1-1ZM15 17c0-1.1.9-2 2-2h2.5c1.1 0 2 .9 2 2v2.5c0 1.1-.9 2-2 2H17c-1.1 0-2-.9-2-2V17Zm4.5 0H17v2.5h2.5V17Zm-15-2c-1.1 0-2 .9-2 2v2.5c0 1.1.9 2 2 2H7c1.1 0 2-.9 2-2V17c0-1.1-.9-2-2-2H4.5Zm0 2H7v2.5H4.5V17ZM15 4.5c0-1.1.9-2 2-2h2.5c1.1 0 2 .9 2 2V7c0 1.1-.9 2-2 2H17c-1.1 0-2-.9-2-2V4.5Zm4.5 0H17V7h2.5V4.5Z" fill="currentColor"></path></svg>
@ -205,7 +205,7 @@ export default {
},
async mounted() {
this.privacyShow = !!this.$isEEUIApp && (await $A.IDBString("cachePrivacyShow")) !== "no";
this.privacyShow = !!this.$isMobileApp && (await $A.IDBString("cachePrivacyShow")) !== "no";
this.email = await $A.IDBString("cacheLoginEmail") || ''
//
if (this.$isSoftware) {
@ -497,7 +497,7 @@ export default {
this.chackServerUrl().catch(_ => {});
$A.IDBSet("cachePrivacyShow", "no")
} else {
$A.eeuiAppGoDesktop()
$A.nativeAppGoDesktop()
}
},

View File

@ -180,7 +180,7 @@
<Draggable
:list="projectDraggableList"
:animation="150"
:disabled="$isEEUIApp || windowTouch || !!projectKeyValue || ownerProjectTabsVisible"
:disabled="$isMobileApp || windowTouch || !!projectKeyValue || ownerProjectTabsVisible"
tag="ul"
item-key="id"
draggable="li:not(.pinned)"
@ -258,7 +258,7 @@
<Loading v-if="projectKeyLoading > 0"/>
<Icon v-else type="ios-search" />
</div>
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.eeuiAppKeyboardHide">
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.nativeAppKeyboardHide">
<Input type="search" v-model="projectKeyValue" :placeholder="$L(`共${projectTotal || cacheProjects.length}个项目,搜索...`)" clearable/>
</Form>
</div>
@ -769,7 +769,7 @@ export default {
* @returns {boolean}
*/
showDownloadClient() {
return !this.$Electron && !this.$isEEUIApp && !!this.clientDownloadUrl
return !this.$Electron && !this.$isMobileApp && !!this.clientDownloadUrl
},
/**
@ -1562,7 +1562,7 @@ export default {
if (silence) {
return; //
}
if (!this.natificationReady && !this.$isEEUIApp) {
if (!this.natificationReady && !this.$isMobileApp) {
return; //
}
if (this.windowActive && data.dialog_id === $A.last(this.dialogIns)?.dialog_id) {
@ -1593,7 +1593,7 @@ export default {
const notificationFuncB = (title, body, userimg) => {
if (this.__notificationId === id) {
this.__notificationId = null
if (this.$isEEUIApp) {
if (this.$isMobileApp) {
emitter.emit('openMobileNotification', {
userid: userid,
title,

View File

@ -552,7 +552,7 @@ export default {
{value: "vote", label: "群投票", sort: 100},
{value: "addProject", label: "创建项目", sort: 110},
{value: "addTask", label: "添加任务", sort: 120},
{value: "scan", label: "扫一扫", sort: 130, show: $A.isEEUIApp},
{value: "scan", label: "扫一扫", sort: 130, show: $A.isMobileApp},
//
{type: 'admin', value: "ldap", label: "LDAP", sort: 160, show: this.userIsAdmin},
@ -1085,7 +1085,7 @@ export default {
this.appPushShow = true;
break;
case 'scan':
$A.eeuiAppScan(this.scanResult);
$A.nativeAppScan(this.scanResult);
break;
case 'word-chain':
case 'vote':

View File

@ -79,24 +79,24 @@
:visibleArrow="false"
placement="top"
popperClass="chat-input-emoji-popover">
<ETooltip slot="reference" ref="emojiTip" :disabled="$isEEUIApp || windowTouch || showEmoji" placement="top" :enterable="false" :content="$L('表情')">
<ETooltip slot="reference" ref="emojiTip" :disabled="$isMobileApp || windowTouch || showEmoji" placement="top" :enterable="false" :content="$L('表情')">
<i class="taskfont">&#xe7ad;</i>
</ETooltip>
<ChatEmoji v-if="showEmoji" @on-select="onSelectEmoji" :searchKey="emojiQuickKey"/>
</EPopover>
<ETooltip v-else ref="emojiTip" :disabled="$isEEUIApp || windowTouch || showEmoji" placement="top" :enterable="false" :content="$L('表情')">
<ETooltip v-else ref="emojiTip" :disabled="$isMobileApp || windowTouch || showEmoji" placement="top" :enterable="false" :content="$L('表情')">
<i class="taskfont" @click="showEmoji=!showEmoji">&#xe7ad;</i>
</ETooltip>
</li>
<!-- @ # -->
<li>
<ETooltip placement="top" :disabled="$isEEUIApp || windowTouch" :enterable="false" :content="$L('选择成员')">
<ETooltip placement="top" :disabled="$isMobileApp || windowTouch" :enterable="false" :content="$L('选择成员')">
<i class="taskfont" @click="onToolbar('user')">&#xe78f;</i>
</ETooltip>
</li>
<li>
<ETooltip placement="top" :disabled="$isEEUIApp || windowTouch" :enterable="false" :content="$L('选择任务')">
<ETooltip placement="top" :disabled="$isMobileApp || windowTouch" :enterable="false" :content="$L('选择任务')">
<i class="taskfont" @click="onToolbar('task')">&#xe7d6;</i>
</ETooltip>
</li>
@ -109,7 +109,7 @@
:visibleArrow="false"
placement="top"
popperClass="chat-input-more-popover">
<ETooltip slot="reference" ref="moreTip" :disabled="$isEEUIApp || windowTouch || showMore" placement="top" :enterable="false" :content="$L('展开')">
<ETooltip slot="reference" ref="moreTip" :disabled="$isMobileApp || windowTouch || showMore" placement="top" :enterable="false" :content="$L('展开')">
<i class="taskfont">&#xe790;</i>
</ETooltip>
<template v-if="!isAiBot">
@ -177,7 +177,7 @@
trigger="manual"
placement="top"
popperClass="chat-input-more-popover">
<ETooltip slot="reference" ref="sendTip" placement="top" :disabled="$isEEUIApp || windowTouch || showMenu" :enterable="false" :content="$L(sendContent)">
<ETooltip slot="reference" ref="sendTip" placement="top" :disabled="$isMobileApp || windowTouch || showMenu" :enterable="false" :content="$L(sendContent)">
<div v-if="loading">
<div class="chat-load">
<Loading/>
@ -562,7 +562,7 @@ export default {
}
}, 1000)
//
if (this.$isEEUIApp) {
if (this.$isMobileApp) {
window.__onPermissionRequest = (type, result) => {
if (type === 'recordAudio' && result === false) {
// Android
@ -609,7 +609,7 @@ export default {
...mapGetters(['getDialogDraft', 'getDialogQuote']),
isEnterSend({cacheKeyboard}) {
if (this.$isEEUIApp) {
if (this.$isMobileApp) {
return cacheKeyboard.send_button_app === 'enter';
} else {
return cacheKeyboard.send_button_desktop === 'enter';
@ -624,7 +624,7 @@ export default {
},
canCall() {
return this.dialogData.type === 'user' && !this.dialogData.bot && this.$isEEUIApp
return this.dialogData.type === 'user' && !this.dialogData.bot && this.$isMobileApp
},
canAnon() {
@ -838,7 +838,7 @@ export default {
if (this.isAiBot) {
return
}
$A.eeuiAppGetLatestPhoto().then(({thumbnail, original}) => {
$A.nativeAppGetLatestPhoto().then(({thumbnail, original}) => {
const size = Math.min(120, Math.max(100, this.$refs.moreFull.clientWidth));
this.maybePhotoStyle = {
width: size + 'px',
@ -1038,7 +1038,7 @@ export default {
},
selectionPlugin: {
onTextSelected: (selectedText) => {
if (this.$isEEUIApp || this.windowTouch) {
if (this.$isMobileApp || this.windowTouch) {
return
}
this.selectedText = !!selectedText.trim()
@ -1170,7 +1170,7 @@ export default {
// Set enterkeyhint
this.$nextTick(_ => {
if (this.$isEEUIApp && this.cacheKeyboard.send_button_app === 'enter') {
if (this.$isMobileApp && this.cacheKeyboard.send_button_app === 'enter') {
this.quill.root.setAttribute('enterkeyhint', 'send')
}
})

View File

@ -94,7 +94,7 @@ export default {
show(v) {
this.$store.state.dialogModalShow = v;
$A.eeuiAppSetScrollDisabled(v && this.windowPortrait)
$A.nativeAppSetScrollDisabled(v && this.windowPortrait)
}
},

View File

@ -142,7 +142,7 @@
<Loading v-if="searchLoad > 0"/>
<Icon v-else type="ios-search" />
</div>
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.eeuiAppKeyboardHide">
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.nativeAppKeyboardHide">
<Input type="search" ref="searchInput" v-model="searchKey" :placeholder="$L('搜索消息')" @on-keyup="onSearchKeyup" clearable/>
<div v-if="searchLoad === 0 && searchResult.length > 0" class="search-total">{{searchLocation}}/{{searchResult.length}}</div>
</Form>
@ -1892,7 +1892,7 @@ export default {
}
this.tempMsgs.push(tempMsg)
//
$A.eeuiAppUploadPhoto({
$A.nativeAppUploadPhoto({
url: $A.apiUrl('dialog/msg/sendfile'),
data: {
dialog_id: tempMsg.dialog_id,
@ -2432,7 +2432,7 @@ export default {
onTouchStart() {
// Android
if (this.keyboardShow) {
$A.eeuiAppSetDisabledUserLongClickSelect(500);
$A.nativeAppSetDisabledUserLongClickSelect(500);
}
},
@ -2600,7 +2600,7 @@ export default {
spinner: 600,
}).then(({data}) => {
if (data.tel) {
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'callTel',
tel: data.tel
});
@ -3547,7 +3547,7 @@ export default {
const {file_uid, file_method} = this.operateItem
if (file_method === "photo") {
try {
await $A.eeuiAppCancelUploadPhoto(file_uid)
await $A.nativeAppCancelUploadPhoto(file_uid)
} catch (e) {
//
}
@ -3932,7 +3932,7 @@ export default {
title,
titleFixed: true,
});
} else if (this.$isEEUIApp) {
} else if (this.$isMobileApp) {
this.$store.dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: title,
@ -4567,7 +4567,7 @@ export default {
autoScrollInto() {
return this.location === "modal"
&& this.$isEEUIApp
&& this.$isMobileApp
&& this.windowPortrait
&& this.$refs.input?.isFocus
},

View File

@ -55,14 +55,14 @@
</Dropdown>
<template v-if="!file.only_view">
<div class="header-icons">
<ETooltip :disabled="$isEEUIApp || windowTouch" :content="$L('文件链接')">
<ETooltip :disabled="$isMobileApp || windowTouch" :content="$L('文件链接')">
<div class="header-icon" @click="handleClick('link')"><i class="taskfont">&#xe785;</i></div>
</ETooltip>
<EPopover v-model="historyShow" trigger="click">
<div class="file-content-history">
<FileHistory :value="historyShow" :file="file" @on-restore="onRestoreHistory"/>
</div>
<ETooltip slot="reference" ref="historyTip" :disabled="$isEEUIApp || windowTouch || historyShow" :content="$L('历史版本')">
<ETooltip slot="reference" ref="historyTip" :disabled="$isMobileApp || windowTouch || historyShow" :content="$L('历史版本')">
<div class="header-icon"><i class="taskfont">&#xe71d;</i></div>
</ETooltip>
</EPopover>

View File

@ -187,7 +187,7 @@ export default {
title,
titleFixed: true,
});
} else if (this.$isEEUIApp) {
} else if (this.$isMobileApp) {
this.$store.dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: title,

View File

@ -298,7 +298,7 @@ export default {
async onOpen(isDirect = false) {
//
let isMeeting = false;
if ($A.isEEUIApp) {
if ($A.isMobileApp) {
isMeeting = this.appMeetingShow;
} else if ($A.Electron) {
const meetingWindow = await $A.Electron.sendAsync("getChildWindow", 'meeting-window')
@ -349,7 +349,7 @@ export default {
delete data.name;
delete data.msgs;
// App 使
if ($A.isEEUIApp) {
if ($A.isMobileApp) {
loader(true);
this.loadNum = 0
this.loadTimer && clearInterval(this.loadTimer)
@ -360,7 +360,7 @@ export default {
clearInterval(this.loadTimer)
loader(false)
}, 1000)
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'startMeeting',
meetingParams: {
name: this.addData.name,

View File

@ -3,7 +3,7 @@
<div :id="id" class="player">
<div class="player-bg" :style="playerStyle"></div>
</div>
<ETooltip :disabled="$isEEUIApp || windowTouch || !username">
<ETooltip :disabled="$isMobileApp || windowTouch || !username">
<div slot="content">
{{username}}
</div>

View File

@ -7,7 +7,7 @@
<Loading v-if="loadProjects > 0"/>
<Icon v-else type="ios-search" />
</div>
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.eeuiAppKeyboardHide">
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.nativeAppKeyboardHide">
<Input type="search" v-model="projectKeyValue" :placeholder="$L(loadProjects > 0 ? '更新中...' : '搜索')" clearable/>
</Form>
</div>

View File

@ -224,7 +224,7 @@ export default {
path: path,
title: this.$L(title),
});
} else if (this.$isEEUIApp) {
} else if (this.$isMobileApp) {
e.preventDefault()
this.$store.dispatch('openAppChildPage', {
pageType: 'app',

View File

@ -20,7 +20,7 @@
</li>
<template v-if="!(windowWidth <= 980 || projectData.cacheParameter.chat) && projectUser.length > 0" v-for="item in projectUser">
<li v-if="item.userid === -1" class="more">
<ETooltip :disabled="$isEEUIApp || windowTouch" :content="$L('共' + (projectData.project_user.length) + '个成员')">
<ETooltip :disabled="$isMobileApp || windowTouch" :content="$L('共' + (projectData.project_user.length) + '个成员')">
<Icon type="ios-more"/>
</ETooltip>
</li>
@ -33,7 +33,7 @@
</ul>
</li>
<li v-if="!projectData.department_readonly" class="project-icon" @click="addTaskOpen(0)">
<ETooltip :disabled="$isEEUIApp || windowTouch" :content="$L('添加任务')">
<ETooltip :disabled="$isMobileApp || windowTouch" :content="$L('添加任务')">
<Icon class="menu-icon" type="md-add" />
</ETooltip>
</li>
@ -121,7 +121,7 @@
<Draggable
:list="columnList"
:animation="150"
:disabled="sortDisabled || isDepartmentReadonly || $isEEUIApp || windowTouch"
:disabled="sortDisabled || isDepartmentReadonly || $isMobileApp || windowTouch"
class="column-list"
tag="ul"
draggable=".column-item"
@ -193,7 +193,7 @@
<Draggable
:list="column.tasks"
:animation="150"
:disabled="sortDisabled || isDepartmentReadonly || $isEEUIApp || windowTouch"
:disabled="sortDisabled || isDepartmentReadonly || $isMobileApp || windowTouch"
class="task-list"
draggable=".task-draggable"
filter=".complete"
@ -240,7 +240,7 @@
<ETooltip
v-if="item.end_at"
:class="['task-time', item.today ? 'today' : '', item.overdue ? 'overdue' : '']"
:disabled="$isEEUIApp || windowTouch"
:disabled="$isMobileApp || windowTouch"
:open-delay="600"
:content="item.end_at">
<div v-if="!item.complete_at"><i class="taskfont">&#xe71d;</i>{{ expiresFormat(item.end_at) }}</div>

View File

@ -103,7 +103,7 @@
<Draggable
:list="data.project_flow_item"
:animation="150"
:disabled="$isEEUIApp || windowTouch"
:disabled="$isMobileApp || windowTouch"
class="taskflow-config-table-list-wrapper"
tag="div"
draggable=".column-border"

View File

@ -13,13 +13,13 @@
<Radio label="daily" :disabled="id > 0 && reportData.type =='weekly'">{{ $L("日报") }}</Radio>
</RadioGroup>
<ButtonGroup v-if="id === 0" class="report-buttongroup">
<ETooltip :disabled="$isEEUIApp || windowTouch" :content="prevCycleText" placement="bottom">
<ETooltip :disabled="$isMobileApp || windowTouch" :content="prevCycleText" placement="bottom">
<Button type="primary" @click="prevCycle">
<Icon type="ios-arrow-back" />
</Button>
</ETooltip>
<div class="report-buttongroup-vertical"></div>
<ETooltip :disabled="$isEEUIApp || windowTouch || reportData.offset >= 0" :content="nextCycleText" placement="bottom">
<ETooltip :disabled="$isMobileApp || windowTouch || reportData.offset >= 0" :content="nextCycleText" placement="bottom">
<Button type="primary" @click="nextCycle" :disabled="reportData.offset >= 0">
<Icon type="ios-arrow-forward" />
</Button>

View File

@ -53,7 +53,7 @@
<Button @click="advanced=!advanced">{{$L('高级选项')}}</Button>
<ul class="advanced-priority">
<li v-for="(item, key) in taskPriority" :key="key">
<ETooltip :disabled="$isEEUIApp || windowTouch" :content="taskPriorityContent(item)">
<ETooltip :disabled="$isMobileApp || windowTouch" :content="taskPriorityContent(item)">
<i
class="taskfont"
:style="{color:item.color}"

View File

@ -17,7 +17,7 @@
<div v-if="parentId == 0" class="priority">
<ul>
<li v-for="(item, key) in taskPriority" :key="key">
<ETooltip v-if="active" :disabled="$isEEUIApp || windowTouch" :content="taskPriorityContent(item)">
<ETooltip v-if="active" :disabled="$isMobileApp || windowTouch" :content="taskPriorityContent(item)">
<i
class="taskfont"
:style="{color:item.color}"
@ -57,7 +57,7 @@
<div class="priority">
<ul>
<li v-for="(item, key) in taskPriority" :key="key">
<ETooltip v-if="active" :disabled="$isEEUIApp || windowTouch" :content="taskPriorityContent(item)">
<ETooltip v-if="active" :disabled="$isMobileApp || windowTouch" :content="taskPriorityContent(item)">
<i
class="taskfont"
:style="{color:item.color}"

View File

@ -175,7 +175,7 @@ export default {
title: title,
titleFixed: true,
});
} else if (this.$isEEUIApp) {
} else if (this.$isMobileApp) {
this.$store.dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: title,

View File

@ -95,7 +95,7 @@
<p v-if="taskDetail.id"><span>{{taskDetail.id}}</span></p>
</div>
<div class="function">
<ETooltip v-if="$Electron" :disabled="$isEEUIApp || windowTouch" :content="$L('独立窗口显示')">
<ETooltip v-if="$Electron" :disabled="$isMobileApp || windowTouch" :content="$L('独立窗口显示')">
<i class="taskfont open" @click="openNewWin">&#xe776;</i>
</ETooltip>
<div v-if="!isDepartmentReadonly" class="menu">
@ -289,7 +289,7 @@
</div>
<ul class="item-content loop">
<li>
<ETooltip :disabled="$isEEUIApp || windowTouch || !taskDetail.loop_at" :content="`${$L('下个周期')}: ${taskDetail.loop_at}`" placement="right">
<ETooltip :disabled="$isMobileApp || windowTouch || !taskDetail.loop_at" :content="`${$L('下个周期')}: ${taskDetail.loop_at}`" placement="right">
<span ref="loopText" @click="!isDepartmentReadonly && onLoop($event)">{{$L(loopLabel(taskDetail.loop))}}</span>
</ETooltip>
</li>
@ -1046,7 +1046,7 @@ export default {
this.ready = true;
this.loadRelatedTasks();
} else {
$A.eeuiAppKeyboardHide()
$A.nativeAppKeyboardHide()
this.timeOpen = false;
this.timeForce = false;
this.loopForce = false;
@ -1926,7 +1926,7 @@ export default {
this.$store.dispatch('openDialog', dialogId).catch(({msg}) => {
$A.modalError(msg);
})
$A.eeuiAppKeyboardHide();
$A.nativeAppKeyboardHide();
}
},
@ -2085,7 +2085,7 @@ export default {
title: `${file.name} (${$A.bytesToSize(file.size)})`,
titleFixed: true,
});
} else if (this.$isEEUIApp) {
} else if (this.$isMobileApp) {
this.$store.dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: `${file.name} (${$A.bytesToSize(file.size)})`,
@ -2361,7 +2361,7 @@ export default {
},
autoScrollInto() {
return this.$isEEUIApp
return this.$isMobileApp
&& this.windowPortrait
&& this.$refs.chatInput?.isFocus
},

View File

@ -41,7 +41,7 @@ export default {
watch: {
show(v) {
$A.eeuiAppSetScrollDisabled(v && this.windowPortrait)
$A.nativeAppSetScrollDisabled(v && this.windowPortrait)
}
},

View File

@ -94,7 +94,7 @@
<ETooltip
v-if="!item.complete_at && item.end_at"
:class="['task-time', item.today ? 'today' : '', item.overdue ? 'overdue' : '']"
:disabled="$isEEUIApp || windowTouch"
:disabled="$isMobileApp || windowTouch"
:open-delay="600"
:content="item.end_at">
<div @click="openTask(item)">{{expiresFormat(item.end_at)}}</div>

View File

@ -451,8 +451,8 @@ export default {
$A.modalConfirm({
content: `是否拨打电话给 ${this.userData.nickname}`,
onOk: () => {
if ($A.isEEUIApp()) {
$A.eeuiAppSendMessage({
if ($A.isMobileApp()) {
$A.nativeAppSendMessage({
action: 'callTel',
tel: this.userData.tel
});

View File

@ -90,7 +90,7 @@
<i class="taskfont">&#xe71f;</i>
<em>{{item.sub_complete}}/{{item.sub_num}}</em>
</div>
<ETooltip v-if="item.end_at" :disabled="$isEEUIApp || windowTouch" :content="item.end_at" placement="right">
<ETooltip v-if="item.end_at" :disabled="$isMobileApp || windowTouch" :content="item.end_at" placement="right">
<div :class="['item-icon', item.today ? 'today' : '', item.overdue ? 'overdue' : '']">
<i class="taskfont">&#xe71d;</i>
<em>{{expiresFormat(item.end_at)}}</em>

View File

@ -1010,7 +1010,7 @@ export default {
fileShow(val) {
if (!val) {
this.browseFile(0)
$A.eeuiAppKeyboardHide()
$A.nativeAppKeyboardHide()
}
},
@ -1508,7 +1508,7 @@ export default {
return;
}
//
if (this.$Electron || this.$isEEUIApp) {
if (this.$Electron || this.$isMobileApp) {
this.openFileSingle(item);
return;
}
@ -1526,7 +1526,7 @@ export default {
title: $A.getFileName(item),
titleFixed: true,
});
} else if (this.$isEEUIApp) {
} else if (this.$isMobileApp) {
this.$store.dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: $A.getFileName(item),

View File

@ -9,7 +9,7 @@
<Loading v-if="searchLoading"/>
<Icon v-else type="ios-search" />
</div>
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.eeuiAppKeyboardHide">
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.nativeAppKeyboardHide">
<Input
v-if="tabActive==='dialog'"
type="search"
@ -60,7 +60,7 @@
</div>
</div>
</div>
<div v-if="$isEEUIApp && !appNotificationPermission" class="messenger-notify-permission" @click="onOpenAppSetting">
<div v-if="$isMobileApp && !appNotificationPermission" class="messenger-notify-permission" @click="onOpenAppSetting">
{{$L('未开启通知权限')}}<i class="taskfont">&#xe733;</i>
</div>
<Scrollbar
@ -356,8 +356,8 @@ export default {
//
this.$nextTick(_ => this.activeNum++)
//
if ($A.isEEUIApp) {
$A.eeuiAppSendMessage({action: 'getNotificationPermission'});
if ($A.isMobileApp) {
$A.nativeAppSendMessage({action: 'getNotificationPermission'});
}
},
@ -1243,7 +1243,7 @@ export default {
},
onOpenAppSetting() {
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'gotoSetting',
});
},

View File

@ -140,7 +140,7 @@ export default {
* 唤醒APP
*/
wakeApp() {
if (!$A.Electron && !$A.isEEUIApp && navigator.userAgent.indexOf("MicroMessenger") === -1) {
if (!$A.Electron && !$A.isMobileApp && navigator.userAgent.indexOf("MicroMessenger") === -1) {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
try {
if (/Android/i.test(navigator.userAgent)) {

View File

@ -113,7 +113,7 @@
<Radio label="close">{{$L('关闭')}}</Radio>
</RadioGroup>
<div class="form-tip">{{$L('任务完成后自动归档。')}}</div>
<ETooltip v-if="formDatum.auto_archived=='open'" placement="right" :disabled="$isEEUIApp || windowTouch">
<ETooltip v-if="formDatum.auto_archived=='open'" placement="right" :disabled="$isMobileApp || windowTouch">
<div class="setting-auto-day">
<Input v-model="formDatum.archived_day" type="number">
<span slot="append">{{$L('天')}}</span>

View File

@ -32,7 +32,7 @@
</li>
</ul>
</div>
<transition :name="$isEEUIApp ? 'mobile-dialog' : 'none'">
<transition :name="$isMobileApp ? 'mobile-dialog' : 'none'">
<div v-if="showContent" class="setting-content">
<MobileNavTitle :title="settingTitleName"/>
<div class="setting-content-title">{{titleNameRoute}}</div>
@ -62,8 +62,8 @@ export default {
},
mounted() {
if (this.$isEEUIApp) {
this.clientVersion = `${window.systemInfo.version} (${$A.eeuiAppLocalVersion()})`
if (this.$isMobileApp) {
this.clientVersion = `${window.systemInfo.version} (${$A.nativeAppLocalVersion()})`
}
},
@ -87,11 +87,11 @@ export default {
{path: 'theme', name: '主题设置'},
]
if (this.$Electron || this.$isEEUIApp) {
if (this.$Electron || this.$isMobileApp) {
menu.push({path: 'keyboard', name: '键盘设置'})
}
if ($A.isDooServer() && this.$isEEUIApp) {
if ($A.isDooServer() && this.$isMobileApp) {
menu.push(...[
{path: 'privacy', name: '隐私政策', divided: true},
{path: 'delete', name: '删除帐号'},
@ -198,7 +198,7 @@ export default {
openPrivacy() {
const url = $A.apiUrl('privacy')
if (this.$isEEUIApp) {
if (this.$isMobileApp) {
this.$store.dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: ' ',

View File

@ -38,7 +38,7 @@
</div>
</FormItem>
</template>
<FormItem v-if="$isEEUIApp" :label="$L('发送按钮')">
<FormItem v-if="$isMobileApp" :label="$L('发送按钮')">
<RadioGroup v-model="formData.send_button_app">
<Radio label="button">{{$L('开启')}}</Radio>
<Radio label="enter">{{$L('关闭')}}</Radio>

View File

@ -0,0 +1,334 @@
<template>
<div class="page-share">
<div class="share-header">
<div class="share-cancel" @click="onCancel">{{ $L('取消') }}</div>
<div class="share-title">{{ $L('发送给') }}</div>
<div class="share-action" @click="onSend" :class="{ disabled: !canSend }">
{{ sendingState || $L('发送') }}
</div>
</div>
<div v-if="loading" class="share-loading">
<Loading :content="$L('加载中')"/>
</div>
<div v-else-if="errorMessage" class="share-error">
<p>{{ errorMessage }}</p>
<Button @click="onCancel">{{ $L('返回') }}</Button>
</div>
<template v-else>
<div class="share-preview">
<div v-if="payload.text" class="share-preview-text">{{ payload.text }}</div>
<div v-if="payload.url" class="share-preview-url">{{ payload.url }}</div>
<div v-if="payload.items && payload.items.length" class="share-preview-items">
<div
v-for="(item, idx) in payload.items"
:key="idx"
class="share-preview-item"
>
<div class="name">{{ item.name }}</div>
<div class="meta">{{ formatSize(item.size) }} · {{ item.mime }}</div>
</div>
</div>
</div>
<div class="share-search">
<Input
v-model="keyword"
:placeholder="$L('搜索会话')"
clearable
@on-change="loadList"
/>
</div>
<div class="share-list">
<div
v-for="item in list"
:key="item.name + '_' + (item.extend && item.extend.dialog_ids)"
class="share-list-item"
:class="{ selected: isSelected(item) }"
@click="toggleSelect(item)"
>
<img class="icon" :src="item.icon" alt=""/>
<div class="name">{{ item.name }}</div>
<div v-if="isSelected(item)" class="check"></div>
</div>
<div v-if="!list.length" class="share-list-empty">{{ $L('暂无会话') }}</div>
</div>
</template>
</div>
</template>
<script>
import Loading from "../components/Loading.vue";
/**
* App 分享接收页二期 WS11
*
* 流程原生 (iOS Share Extension / Android Intent) 写共享 payload 到本地
* deep link `dootask://share?token=<x>` __handleLink 跳本页
* $A.nativeAppGetSharedPayload(token) 拿元数据
* api/users/share/list 渲染会话/用户列表type='text' 接口仅返回会话简洁
* 用户选会话 POST api/dialog/msg/sendfiles 上传
* $A.nativeAppClearSharedPayload(token) 清理 + 跳目标会话
*/
export default {
name: "share",
components: { Loading },
data() {
return {
token: "",
payload: { items: [], text: undefined, url: undefined },
list: [],
keyword: "",
selectedIds: [],
loading: true,
errorMessage: "",
sendingState: "",
};
},
computed: {
canSend() {
if (!this.selectedIds.length) return false;
const hasAttachment =
(this.payload.items && this.payload.items.length > 0) ||
this.payload.text ||
this.payload.url;
return !!hasAttachment && !this.sendingState;
},
},
mounted() {
this.token = String(this.$route.query.token || "");
if (!this.token) {
this.errorMessage = this.$L("缺少分享凭据");
this.loading = false;
return;
}
this.loadPayload();
},
methods: {
async loadPayload() {
try {
const result = await this.callBridge("getSharedPayload", { token: this.token });
if (!result) {
this.errorMessage = this.$L("分享内容不存在或已过期");
this.loading = false;
return;
}
this.payload = result;
await this.loadList();
} catch (e) {
this.errorMessage = (e && e.message) || this.$L("加载失败");
} finally {
this.loading = false;
}
},
async loadList() {
// type='text'
// "" type='file'
const params = { url: "users/share/list", data: { type: "text", key: this.keyword } };
try {
const { data } = await this.$store.dispatch("call", params);
const lists = Array.isArray(data) ? data : (data && data.lists) || [];
this.list = lists.filter(it => it.type === "item");
} catch (e) {
this.errorMessage = (e && e.msg) || this.$L("加载会话列表失败");
}
},
isSelected(item) {
const id = item.extend && item.extend.dialog_ids;
return !!id && this.selectedIds.indexOf(id) !== -1;
},
toggleSelect(item) {
const id = item.extend && item.extend.dialog_ids;
if (!id) return;
const idx = this.selectedIds.indexOf(id);
if (idx === -1) this.selectedIds.push(id);
else this.selectedIds.splice(idx, 1);
},
formatSize(bytes) {
if (!bytes && bytes !== 0) return "";
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
},
callBridge(method, args) {
if (window.__nativeBridge && typeof window.__nativeBridge.call === "function") {
return window.__nativeBridge.call(method, args);
}
return Promise.resolve(null);
},
async onSend() {
if (!this.canSend) return;
const dialogIds = this.selectedIds.join(",");
try {
if (this.payload.items && this.payload.items.length) {
this.sendingState = this.$L("正在发送");
for (const item of this.payload.items) {
await this.uploadOne(dialogIds, item);
}
}
// /URL
const extraText = [this.payload.text, this.payload.url].filter(Boolean).join("\n");
if (extraText) {
await this.$store.dispatch("call", {
url: "dialog/msg/sendtext",
method: "post",
data: { dialog_ids: dialogIds, text: extraText, text_type: "text" },
});
}
//
await this.callBridge("clearSharedPayload", { token: this.token });
//
this.$store.state.dialogId = this.selectedIds[0];
this.$router.replace({ name: "manage-messenger" });
} catch (e) {
$A.modalError({ content: (e && e.msg) || this.$L("发送失败") });
this.sendingState = "";
}
},
async uploadOne(dialogIds, item) {
// file:// uri Blob multipart upload
const res = await fetch(item.uri);
const blob = await res.blob();
const form = new FormData();
form.append("dialog_ids", dialogIds);
form.append("files", blob, item.name || "shared");
await this.$store.dispatch("call", {
url: "dialog/msg/sendfiles",
method: "post",
data: form,
headers: { "Content-Type": "multipart/form-data" },
});
},
async onCancel() {
if (this.token) {
try { await this.callBridge("clearSharedPayload", { token: this.token }); } catch (e) { /* ignore */ }
}
this.$router.replace({ name: "index" });
},
},
};
</script>
<style lang="scss" scoped>
.page-share {
height: 100vh;
display: flex;
flex-direction: column;
background: var(--ee-bg-color, #f7f8fa);
.share-header {
display: flex;
align-items: center;
padding: 12px 16px;
background: #fff;
border-bottom: 1px solid #eaecef;
.share-title {
flex: 1;
text-align: center;
font-size: 16px;
font-weight: 600;
}
.share-cancel,
.share-action {
font-size: 14px;
color: #1890ff;
cursor: pointer;
}
.share-action.disabled {
color: #c0c4cc;
cursor: not-allowed;
}
}
.share-loading,
.share-error {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px;
text-align: center;
}
.share-preview {
background: #fff;
padding: 12px 16px;
border-bottom: 1px solid #eaecef;
.share-preview-text,
.share-preview-url {
padding: 8px 0;
font-size: 14px;
word-break: break-all;
}
.share-preview-url { color: #1890ff; }
.share-preview-items {
display: flex;
flex-direction: column;
gap: 6px;
}
.share-preview-item {
padding: 6px 8px;
background: #f0f2f5;
border-radius: 4px;
.name { font-size: 13px; }
.meta { font-size: 11px; color: #8c8c8c; margin-top: 2px; }
}
}
.share-search {
padding: 8px 12px;
background: #fff;
border-bottom: 1px solid #eaecef;
}
.share-list {
flex: 1;
overflow-y: auto;
background: #fff;
.share-list-item {
display: flex;
align-items: center;
padding: 10px 16px;
border-bottom: 1px solid #f0f2f5;
cursor: pointer;
&.selected { background: #e6f7ff; }
.icon {
width: 36px;
height: 36px;
border-radius: 18px;
margin-right: 12px;
object-fit: cover;
}
.name {
flex: 1;
font-size: 14px;
}
.check {
font-weight: 600;
color: #1890ff;
}
}
.share-list-empty {
padding: 32px;
text-align: center;
color: #8c8c8c;
font-size: 13px;
}
}
}
</style>

View File

@ -3,7 +3,7 @@
<PageTitle :title="pageName"/>
<Loading v-if="loadIng > 0"/>
<template v-else-if="fileInfo">
<FilePreview v-if="isPreview" :code="code" :file="fileInfo" :historyId="historyId" :headerShow="!$isEEUIApp"/>
<FilePreview v-if="isPreview" :code="code" :file="fileInfo" :historyId="historyId" :headerShow="!$isMobileApp"/>
<FileContent v-else v-model="fileShow" :file="fileInfo"/>
</template>
</div>

View File

@ -35,7 +35,7 @@ export default {
return {
loadIng: 0,
info: null,
showHeader: !$A.isEEUIApp,
showHeader: !$A.isMobileApp,
}
},
mounted() {

View File

@ -14,6 +14,11 @@ export default [
path: '/meeting/:meetingId?/:sharekey?',
component: () => import('./pages/meeting.vue'),
},
{
name: 'share',
path: '/share',
component: () => import('./pages/share.vue'),
},
{
name: 'manage',
path: '/manage',

View File

@ -35,7 +35,7 @@ export default {
labelWidth: windowWidth > 576 ? 'auto' : '',
}
$A.eeuiAppSendMessage({
$A.nativeAppSendMessage({
action: 'windowSize',
width: windowWidth,
height: windowHeight,
@ -48,10 +48,11 @@ export default {
window.addEventListener('message', ({data}) => {
data = $A.jsonParse(data);
if (data.action === 'eeuiAppSendMessage') {
// 兼容:前端内部 iframe/postMessage 转 nativeAppSendMessage 的中转桥
if (data.action === 'nativeAppSendMessage' || data.action === 'eeuiAppSendMessage') {
const items = $A.isArray(data.data) ? data.data : [data.data];
items.forEach(item => {
$A.eeuiAppSendMessage(item);
$A.nativeAppSendMessage(item);
})
}
})
@ -177,7 +178,7 @@ export default {
if (!state.isFirstPage) {
return resolve(null)
}
$A.eeuiAppGetSafeAreaInsets().then(async data => {
$A.nativeAppGetSafeAreaInsets().then(async data => {
data.top = data.top || state.safeAreaSize?.data?.top || 0
data.bottom = data.bottom || state.safeAreaSize?.data?.bottom || 0
const proportion = data.height / window.outerHeight
@ -483,8 +484,8 @@ export default {
action: 'createDownload',
url
});
} else if ($A.isEEUIApp) {
$A.eeuiAppOpenWeb(url);
} else if ($A.isMobileApp) {
$A.nativeAppOpenWeb(url);
} else {
window.open(url)
}
@ -557,7 +558,7 @@ export default {
return;
}
if (!$A.dark.utils.supportMode()) {
if ($A.isEEUIApp) {
if ($A.isMobileApp) {
$A.modalWarning("仅Android设置支持主题功能");
} else {
$A.modalWarning("仅客户端或Chrome浏览器支持主题功能");
@ -600,8 +601,8 @@ export default {
state.themeName = $A.dark.isDarkEnabled() ? 'dark' : 'light'
window.localStorage.setItem("__system:themeConf__", state.themeConf)
//
if ($A.isEEUIApp) {
$A.eeuiAppSendMessage({
if ($A.isMobileApp) {
$A.nativeAppSendMessage({
action: 'updateTheme',
themeName: state.themeName,
themeDefault: {
@ -771,23 +772,14 @@ export default {
state.userId = userInfo.userid;
state.userToken = userInfo.token;
state.userIsAdmin = $A.inArray('admin', userInfo.identity);
if ($A.isSubElectron || ($A.isEEUIApp && !state.isFirstPage)) {
if ($A.isSubElectron || ($A.isMobileApp && !state.isFirstPage)) {
// 子窗口Electron、不是第一个页面App 不保存
} else {
await $A.IDBSet("userInfo", state.userInfo);
}
//
$A.eeuiAppSendMessage({
action: 'userChatList',
language: $A.eeuiAppConvertLanguage(),
url: $A.mainUrl('api/users/share/list') + `?token=${state.userToken}`
});
$A.eeuiAppSendMessage({
action:"userUploadUrl",
dirUrl: $A.mainUrl('api/file/content/upload') + `?token=${state.userToken}`,
chatUrl: $A.mainUrl('api/dialog/msg/sendfiles') + `?token=${state.userToken}`,
});
//
// 旧 EEUI 用 userChatList/userUploadUrl 把 share/list 和 sendfiles 接口
// 通过 App Group 同步给 iOS Share Extension 内嵌 UI新移动端 /share 路由
// 由 WebView 内主前端直接调用接口,无需提前同步。
resolve()
})
},
@ -1322,7 +1314,7 @@ export default {
selectclose: "true",
channel,
}
$A.eeuiAppSetVariate(`location::${channel}`, "");
$A.nativeAppSetVariate(`location::${channel}`, "");
const url = $A.urlAddParams(window.location.origin + '/tools/map/index.html', Object.assign(params, objects || {}))
dispatch('openAppChildPage', {
pageType: 'app',
@ -1335,9 +1327,9 @@ export default {
},
callback: ({status}) => {
if (status === 'pause') {
const data = $A.jsonParse($A.eeuiAppGetVariate(`location::${channel}`));
const data = $A.jsonParse($A.nativeAppGetVariate(`location::${channel}`));
if (data.point) {
$A.eeuiAppSetVariate(`location::${channel}`, "");
$A.nativeAppSetVariate(`location::${channel}`, "");
if (data.distance > objects.radius) {
$A.modalError(`你选择的位置「${data.title}」不在签到范围内`)
return
@ -1367,7 +1359,7 @@ export default {
objects.params.showProgress = !isLocalHost(objects.params.url)
}
$A.eeuiAppOpenPage(objects)
$A.nativeAppOpenPage(objects)
},
/**

View File

@ -75,7 +75,7 @@ export function openFileInClient(vm, item, options = {}) {
return;
}
if (vm.$isEEUIApp) {
if (vm.$isMobileApp) {
vm.$store.dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: finalTitle,

View File

@ -54,11 +54,11 @@ function __callData(key, requestData, state) {
} else {
deleted_id = []
}
if ($A.isEEUIApp) {
if ($A.isMobileApp) {
hasUpdate = state.isFirstPage
}
if (hasUpdate) {
if ($A.isSubElectron || ($A.isEEUIApp && !state.isFirstPage)) {
if ($A.isSubElectron || ($A.isMobileApp && !state.isFirstPage)) {
// 子窗口Electron、不是第一个页面App 不保存
} else {
await $A.IDBSet("callAt", state.callAt)

@ -1 +1 @@
Subproject commit 97ac53c3a932041bb8d3fde41a5c6822fb2d95a0
Subproject commit 41d9889b79a7aaab644e1f99c5b16b16ce286881

View File

@ -154,7 +154,7 @@ interface DooTaskGlobal {
/** 是否 Electron 子窗口 */
isSubElectron: boolean;
/** 是否 EEUI App 环境 */
isEEUIApp: boolean;
isMobileApp: boolean;
/** 是否 Electron 环境 */
isElectron: boolean;
/** 是否客户端软件环境Electron 或 EEUI */
@ -574,81 +574,81 @@ interface DooTaskGlobal {
/** 获取eeui模块Promise */
eeuiModulePromise(name?: string): Promise<any>;
/** 获取eeui版本号 */
eeuiAppVersion(): string | undefined;
nativeAppVersion(): string | undefined;
/** 获取本地软件版本号 */
eeuiAppLocalVersion(): string | undefined;
nativeAppLocalVersion(): string | undefined;
/** Alert 弹窗 */
eeuiAppAlert(object: any, callback?: (result: any) => void): void;
nativeAppAlert(object: any, callback?: (result: any) => void): void;
/** Toast 提示 */
eeuiAppToast(object: any): void;
nativeAppToast(object: any): void;
/** 相对地址基于当前地址补全 */
eeuiAppRewriteUrl(val: string): string | undefined;
nativeAppRewriteUrl(val: string): string | undefined;
/** 获取页面信息 */
eeuiAppGetPageInfo(pageName?: string): any;
nativeAppGetPageInfo(pageName?: string): any;
/** 打开app新页面 */
eeuiAppOpenPage(object: Record<string, any>, callback?: (result: any) => void): void;
nativeAppOpenPage(object: Record<string, any>, callback?: (result: any) => void): void;
/** 使用系统浏览器打开网页 */
eeuiAppOpenWeb(url: string): void;
nativeAppOpenWeb(url: string): void;
/** 拦截返回按键事件仅支持android、iOS无效 */
eeuiAppSetPageBackPressed(object: any, callback?: (result: any) => void): void;
nativeAppSetPageBackPressed(object: any, callback?: (result: any) => void): void;
/** 返回手机桌面 */
eeuiAppGoDesktop(): void;
nativeAppGoDesktop(): void;
/** 打开屏幕常亮 */
eeuiAppKeepScreenOn(): void;
nativeAppKeepScreenOn(): void;
/** 关闭屏幕常亮 */
eeuiAppKeepScreenOff(): void;
nativeAppKeepScreenOff(): void;
/** 隐藏软键盘 */
eeuiAppKeyboardHide(): void;
nativeAppKeyboardHide(): void;
/** 给app发送消息 */
eeuiAppSendMessage(object: any): void;
nativeAppSendMessage(object: any): void;
/** 设置浏览器地址 */
eeuiAppSetUrl(url: string): void;
nativeAppSetUrl(url: string): void;
/** 生成webview快照 */
eeuiAppGetWebviewSnapshot(callback: (result: any) => void): void;
nativeAppGetWebviewSnapshot(callback: (result: any) => void): void;
/** 显示webview快照 */
eeuiAppShowWebviewSnapshot(): void;
nativeAppShowWebviewSnapshot(): void;
/** 隐藏webview快照 */
eeuiAppHideWebviewSnapshot(): void;
nativeAppHideWebviewSnapshot(): void;
/** 扫码(成功时回调扫码文本) */
eeuiAppScan(callback: (text: string) => void): void;
nativeAppScan(callback: (text: string) => void): void;
/** 检查更新 */
eeuiAppCheckUpdate(): void;
nativeAppCheckUpdate(): void;
/** 获取主题名称 light|dark */
eeuiAppGetThemeName(): string | undefined;
nativeAppGetThemeName(): string | undefined;
/** 判断软键盘是否可见 */
eeuiAppKeyboardStatus(): boolean | undefined;
nativeAppKeyboardStatus(): boolean | undefined;
/** 设置全局变量 */
eeuiAppSetVariate(key: string, value: any): void;
nativeAppSetVariate(key: string, value: any): void;
/** 获取全局变量 */
eeuiAppGetVariate(key: string, defaultVal?: any): any;
nativeAppGetVariate(key: string, defaultVal?: any): any;
/** 设置缓存数据 */
eeuiAppSetCachesString(key: string, value: string, expired?: number): void;
nativeAppSetCachesString(key: string, value: string, expired?: number): void;
/** 获取缓存数据 */
eeuiAppGetCachesString(key: string, defaultVal?: string): string | undefined;
nativeAppGetCachesString(key: string, defaultVal?: string): string | undefined;
/** 是否长按内容震动仅支持android、iOS无效 */
eeuiAppSetHapticBackEnabled(val: boolean): void;
nativeAppSetHapticBackEnabled(val: boolean): void;
/** 禁止长按选择仅支持android、iOS无效传毫秒数则临时禁止 */
eeuiAppSetDisabledUserLongClickSelect(val: boolean | number | string): void;
nativeAppSetDisabledUserLongClickSelect(val: boolean | number | string): void;
/** 复制文本 */
eeuiAppCopyText(text: string): void;
nativeAppCopyText(text: string): void;
/** 设置是否禁止滚动 */
eeuiAppSetScrollDisabled(disabled: boolean): void;
nativeAppSetScrollDisabled(disabled: boolean): void;
/** 设置应用程序级别的摇动撤销仅支持iOS、android无效 */
eeuiAppShakeToEditEnabled(enabled: boolean): void;
nativeAppShakeToEditEnabled(enabled: boolean): void;
/** 获取最新一张照片 */
eeuiAppGetLatestPhoto(expiration?: number, timeout?: number): Promise<any>;
nativeAppGetLatestPhoto(expiration?: number, timeout?: number): Promise<any>;
/** 上传照片params 参数:{url,data,headers,path,fieldName,onReady?} */
eeuiAppUploadPhoto(params: Record<string, any>, timeout?: number): Promise<any>;
nativeAppUploadPhoto(params: Record<string, any>, timeout?: number): Promise<any>;
/** 取消上传照片 */
eeuiAppCancelUploadPhoto(id: any): Promise<any>;
nativeAppCancelUploadPhoto(id: any): Promise<any>;
/** 获取导航栏和状态栏高度 */
eeuiAppGetSafeAreaInsets(): Promise<any>;
nativeAppGetSafeAreaInsets(): Promise<any>;
/** 获取当前语言zh -> zh-Hans 等映射) */
eeuiAppConvertLanguage(): string;
nativeAppConvertLanguage(): string;
/** 获取设备信息 */
eeuiAppGetDeviceInfo(): Promise<any>;
nativeAppGetDeviceInfo(): Promise<any>;
/** 判断是否窗口化 */
eeuiAppIsWindowed(): Promise<boolean>;
nativeAppIsWindowed(): Promise<boolean>;
}
/** DooTask 全局工具对象jQuery 实例 + $.extend 扩展方法) */