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

293 lines
10 KiB
Vue
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.

<template>
<div class="common-right-bottom">
<div v-if="showSSO" class="common-right-bottom-link" @click="useSSOLogin">
{{ $L('使用 SSO 登录') }}
</div>
<a v-if="showDown && clientDownloadUrl" class="common-right-bottom-link" :href="clientDownloadUrl" target="_blank">
<Icon type="md-download"/>
{{ $L('客户端下载') }}
</a>
<a v-if="showPrivacy" class="common-right-bottom-link" target="_blank" :href="$A.apiUrl('privacy')">
{{ $L('隐私政策') }}
</a>
<Modal
v-model="updateShow"
:closable="false"
:mask-closable="false"
class-name="common-right-bottom-notification">
<div slot="header">
<div class="notification-head">
<div class="notification-title">{{$L('发现新版本')}}</div>
<Tag color="volcano">v{{systemVersion}} -&gt; v{{updateVersion}}</Tag>
</div>
<div v-if="$Platform === 'mac'" class="notification-tip">{{$L('离最新版本只有一步之遥了重新启动应用即可完成更新')}}</div>
</div>
<Scrollbar class-name="notification-body">
<VMPreview :value="updateNote"/>
</Scrollbar>
<div slot="footer" class="adaption">
<Button type="default" @click="updateShow=false">{{$L('稍后')}}</Button>
<Button type="primary" :loading="updateIng" @click="updateQuitAndInstall">{{$L($Platform === 'mac' ? '重新启动' : '立即升级')}}</Button>
</div>
</Modal>
</div>
</template>
<script>
const VMPreview = () => import('./VMEditor/preview');
import axios from "axios";
import emitter from "../store/events";
import {mapState} from "vuex";
export default {
name: 'RightBottom',
components: {VMPreview},
data() {
return {
loadIng: 0,
apiVersion: '',
systemVersion: window.systemInfo.version,
updateVersion: '',
updateNote: '',
updateShow: false,
updateBottomShow: false,
updateIng: false,
}
},
mounted() {
this.prefetchResources()
this.checkVersion()
//
if (this.$Electron) {
emitter.on('updateNotification', this.onUpdateShow);
this.$Electron.listener('updateDownloaded', info => {
this.$store.state.clientNewVersion = info.version
this.updateVersion = info.version;
this.updateNote = info.releaseNotes || this.$L('没有更新描述。');
this.updateShow = !$A.strExists(this.updateNote, `[${this.updateVersion}-Silence]`);
})
}
emitter.on('openDownloadClient', this.openDownloadClient);
},
beforeDestroy() {
emitter.off('updateNotification', this.onUpdateShow);
emitter.off('openDownloadClient', this.openDownloadClient);
},
watch: {
updateShow(show) {
if (show) {
this.updateBottomShow = true
}
}
},
computed: {
...mapState(['clientDownloadUrl']),
showSSO() {
return this.$isSoftware && ['login'].includes(this.routeName)
},
showDown() {
if (this.$Electron || this.$isMobileApp || this.windowTouch) {
return false
}
return this.routeName === 'login'
},
showPrivacy() {
return $A.isDooServer() && this.$isMobileApp && ['login'].includes(this.routeName)
}
},
methods: {
onUpdateShow() {
this.updateShow = true
},
openDownloadClient() {
if (this.clientDownloadUrl) {
window.open(this.clientDownloadUrl, '_blank');
}
},
isNotServer() {
let apiHome = $A.getDomain(window.systemInfo.apiUrl)
return this.$isSoftware && (apiHome == "" || apiHome == "public")
},
prefetchResources() {
if (this.isNotServer()) {
return;
}
if (this.$Electron && $A.$isSubElectron) {
return; // 客户端子窗口 不预加载
}
axios.get($A.apiUrl('system/prefetch')).then(({status, data}) => {
if (status === 200) {
data.forEach(url => {
const script = document.createElement('link')
script.rel = 'prefetch'
script.href = url
script.onload = () => {
document.head.removeChild(script)
}
script.onerror = () => {
document.head.removeChild(script)
}
document.head.appendChild(script)
})
}
}).catch(_ => { })
},
checkVersion() {
if (this.isNotServer()) {
return;
}
axios.get($A.apiUrl('system/version')).then(({status, data}) => {
if (status === 200) {
this.apiVersion = data.version || ''
// 检查接口版本
if (this.compareVersion(this.apiVersion, '0.19.0') === -1) {
$A.modalWarning({
title: '温馨提示',
message: `服务器(${$A.mainDomain()})接口版本过低,部分功能可能无法正常使用。`,
});
}
if (this.$Electron) {
// 客户端提示更新
this.$Electron.sendMessage('updateCheckAndDownload', {
apiVersion: this.apiVersion
})
} else {
// 网页端提示下载
this.getDownloadUrl(data.publish)
}
}
}).catch(_ => {
// console.log('获取版本失败')
})
//
this.__checkVersion && clearTimeout(this.__checkVersion)
this.__checkVersion = setTimeout(this.checkVersion, 600 * 1000)
},
getDownloadUrl(publish) {
if (!$A.isJson(publish)) {
return;
}
//
switch (publish.provider) {
case 'generic':
this.$store.state.clientDownloadUrl = `${publish.url}/latest`
break;
case 'github':
(async _ => {
let key = "cacheAppdown::" + this.apiVersion
let cache = await $A.IDBJson(key);
let timeout = 600;
if (cache.time && cache.time + timeout > $A.dayjs().unix()) {
this.$store.state.clientDownloadUrl = cache.data.html_url;
return;
}
//
if (this.loadIng > 0) {
return;
}
this.loadIng++;
axios.get(`https://api.github.com/repos/${publish.owner}/${publish.repo}/releases`).then(({status, data}) => {
this.loadIng--;
if (status === 200 && $A.isArray(data)) {
cache.time = $A.dayjs().unix()
cache.data = data.find(({tag_name}) => this.compareVersion(this.tagVersion(tag_name), this.apiVersion) === 0) || {}
$A.IDBSave(key, cache);
this.$store.state.clientDownloadUrl = cache.data.html_url;
}
}).catch(() => {
this.loadIng--;
});
})()
break;
}
},
updateQuitAndInstall() {
this.updateIng = true
setTimeout(() => {
this.$Electron.sendMessage('updateQuitAndInstall', {
updateTitle: this.$L('正在安装更新,请稍候...')
})
}, 301)
},
useSSOLogin() {
emitter.emit('useSSOLogin', true);
},
tagVersion(tag) {
return tag ? $A.leftDelete(tag.toLowerCase(), "v") : ''
},
compareVersion(version1, version2) {
let pA = 0, pB = 0;
// 版本号完全相同
if (version1 === version2) {
return 0
}
// 寻找当前区间的版本号
const findDigit = (str, start) => {
let i = start;
while (str[i] !== '.' && i < str.length) {
i++;
}
return i;
}
while (pA < version1.length && pB < version2.length) {
const nextA = findDigit(version1, pA);
const nextB = findDigit(version2, pB);
const numA = +version1.substr(pA, nextA - pA);
const numB = +version2.substr(pB, nextB - pB);
if (numA !== numB) {
return numA > numB ? 1 : -1;
}
pA = nextA + 1;
pB = nextB + 1;
}
// 若arrayA仍有小版本号
while (pA < version1.length) {
const nextA = findDigit(version1, pA);
const numA = +version1.substr(pA, nextA - pA);
if (numA > 0) {
return 1;
}
pA = nextA + 1;
}
// 若arrayB仍有小版本号
while (pB < version2.length) {
const nextB = findDigit(version2, pB);
const numB = +version2.substr(pB, nextB - pB);
if (numB > 0) {
return -1;
}
pB = nextB + 1;
}
// 版本号完全相同
return 0;
},
}
};
</script>