Compare commits

...

72 Commits
v1.9.66 ... pro

Author SHA1 Message Date
kuaifan
8cfcc9048e release: v1.10.38 2026-10-29 01:05:23 +00:00
kuaifan
af5fa5d828 fix(ai-assistant): 打开 AI 助手不再卡顿,切换场景不丢会话
- 打开助手不再等待历史列表加载,列表后台加载
- 会话列表接口只返回最近 20 条
- 浮动按钮点击判定放宽到 500ms 内移动小于 5px
- 每个场景保存时只保留最近 200 条会话,超出的连同图片一起清理
- 切换场景前立即保存当前会话;原场景还在生成的回复照常写入并保存
2026-10-29 01:01:10 +00:00
kuaifan
1c0228f7e0 fix(electron): 打包工具白名单支持 app-builder-lib 26.18.0 2026-10-28 17:38:26 +00:00
kuaifan
775ad00cca release: v1.10.35 2026-10-28 17:08:22 +00:00
kuaifan
32fd99b5d0 fix(dialog): 移动端搜索消息栏去掉圆角,背景与标题栏一致 2026-10-28 17:01:46 +00:00
kuaifan
8aac85a40a fix(dialog): iOS 加载历史消息改为等手势空闲后再插入,不再断触
- 记录手指触摸与原生滚动状态,iOS 上等手指离开、惯性滚动停止后再插入历史消息并补偿位置
- 加载期间阻止重复触发加载,切换对话后跳过位置补偿
- getDialogMsgs 的 save_before 支持返回 Promise
2026-10-28 16:52:34 +00:00
kuaifan
a1e79204f8 fix(dialog): Safari 中加载失败的消息图片滚动时不再反复请求
- 失败图片按地址登记,消息回收重挂载时直接渲染占位图,不再请求
- 文本消息图片改为 inline-block,失败后保持原占位尺寸,避免高度塌陷触发列表反复测量
- 失败占位图点击可重试加载
2026-10-28 16:46:00 +00:00
kuaifan
cb35aaca2b perf(dialog): 消息列表滚动不再触发全部消息重渲染,Markdown 转换增加缓存
滚动与渲染区间变化时 Wrapper 及所有可见消息被反复重渲染:
- 已读判断收拢到 DialogWrapper.onVisibleRead,scrollIng、readEnabled 不再经 extraProps 下发
- 模板改读 scrollTailFar / scrollOffsetNear 布尔值,仅越过阈值时才触发渲染
- positionMsg 改依赖 unreadOneAbove,startMsgId 随区间变化不再带动 Wrapper 重渲染

Markdown:
- MarkdownConver 结果按原文做 LRU 缓存(200 条)
- 未标注语言的代码块只在常用语言子集中自动识别

移动端 4 倍降速滚动 8 秒实测:消息组件更新 1700 → 25 次,Wrapper 重渲染约 90 → 10 次,平均帧率 12 → 22。
2026-10-28 16:00:18 +00:00
kuaifan
c75d7a95f5 perf(project): 看板列拆分为 ProjectColumn 组件,列内变化不再触发整个面板重渲染
列与任务卡片原先写在 ProjectPanel 的同一个渲染函数里,卡片可见状态、倒计时等任一变化都会带动全部列的 draggable、Scrollbar 等插槽组件重渲染:
- 新增 ProjectColumn,每列自行维护列与任务的可见状态,面板只通知其检查可视区
- 任务可见性计算抽成 utils/taskVisibility.js,看板与表格共用
- 跨列拖入的任务落下即标记可见,避免先显示空白占位
- 搜索、筛选、打开任务等面板级逻辑通过 provide 的 projectPanel 读取
2026-10-28 14:26:19 +00:00
kuaifan
db66b7b215 perf(project): 看板列头菜单改为首次展开时才渲染菜单项
每列菜单原先预先挂载约 17 个菜单项,18 列的项目约占面板一半的 watcher。改为记录已展开过的列,首次展开时再渲染。
移动端 4 倍降速实测,18 列项目打开时长任务约减半(1357 → 694ms)。
2026-10-28 14:26:19 +00:00
kuaifan
093a1e9afb feat(project): 移动端 App 内项目列表与详情切换增加右侧滑入/滑出动画
列表与详情为同一路由页面,路由级动画不会触发,改在项目页内部用 transition 处理:
- 滑入期间保持列表与底部选项卡,结束后再隐藏,避免下层页面高度跳变
- 动画期间详情固定覆盖于列表及选项卡之上,按实际位置换算 top
- 返回时等滑出动画结束再清空当前项目数据
2026-10-28 13:22:25 +00:00
kuaifan
9d07f88e8c perf(task): 移动端打开任务详情时先播放打开动画再挂载内容,挂载前显示任务头部、标题与属性骨架占位 2026-10-28 13:03:01 +00:00
kuaifan
2556c53fbc feat(dialog): 消息操作菜单的复制项精简为「复制 / 选择复制」,新增全屏选择复制页
- 移除「复制原文 / 复制选择 / 复制全部」;「选择复制」打开复制页,可自由选字、底部按钮复制全部,Markdown 消息支持预览 / 原文切换
- 复制页标题与其他会话抽屉一致(居中标题),Markdown 预览沿用消息气泡的深色模式处理
- 移动端打开菜单后不再允许长按选字,移除 selectionchange 监听与 operateEnter
- 桌面端保留在消息中选字,直接用键盘复制
- 更新语言包与 ai-kb 移动端手势说明
2026-10-28 11:56:06 +00:00
kuaifan
10378850d2 perf(dialog): 长按消息弹出操作菜单时仅重渲染目标消息,菜单状态改为 provide 下发、列表参数改为计算属性保持引用稳定,状态与位置合并到同一帧更新 2026-10-28 10:07:43 +00:00
kuaifan
58a0180caa feat(setting): 移动端设置页改版,顶部个人资料卡、图标与右箭头、右侧显示当前值、按语义分组,退出登录改为底部按钮
- 引入 ionicons 8 新版图标,新增全局 IonIcon 组件(按需引入 SVG 内联渲染,不走 CDN)
- 桌面端设置菜单保持不变
- 同步 ai-kb 设置入口与个人设置路径
2026-10-28 09:49:06 +00:00
kuaifan
1fb1062056 perf(dialog): 移动端会话窗口先播放打开动画再挂载内容,挂载前显示导航与消息骨架占位;关闭时先开始动画再清除会话ID,避免动画首帧卡顿与划入时空白 2026-10-28 09:29:58 +00:00
kuaifan
b418e036dd feat(manage): 切换页面后返回时记忆滚动位置,消息与通讯录分别记忆 2026-10-28 08:48:26 +00:00
kuaifan
cdeef084da fix(file): 文件页触屏点击残留 hover、移动端不再悬停显示刷新按钮、窄屏搜索框与标题间距过近,列表模式表格高度改为跟随容器 2026-10-28 08:48:23 +00:00
kuaifan
9fce053b93 feat(mobile): App 竖屏从应用进入设置/日历/文件改为右侧划入、返回划出,并沉浸底部安全区
- 划入期间保留底部选项卡、划入页置顶覆盖,避免高度跳动与结束时闪帧
- 返回应用页时恢复其滚动位置
2026-10-28 08:48:19 +00:00
kuaifan
8ded349077 fix(app): 更新 resources/app 子模块(修复小米设备收不到推送、切后台出现空白 DooTask 通知) 2026-10-28 07:09:09 +00:00
kuaifan
85b5abf55e fix(cmd): ./cmd expo dev 退出后 vite 残留占用端口(改为直接运行 vite 入口,退出时可正常结束) 2026-10-28 03:00:11 +00:00
kuaifan
edb2e539a5 feat(cmd): ./cmd expo dev 支持透传参数给 expo start(如 -c 清除 Metro 缓存) 2026-10-28 02:43:59 +00:00
kuaifan
7043c4efa6 fix(app): 更新 resources/app 子模块(修复 iOS 无法连接公网 IP / 域名 http 服务器) 2026-10-28 01:55:47 +00:00
kuaifan
3aeecc4769 fix(contacts): 部门视角下长按联系人弹出菜单后不再继续进入联系人 2026-10-25 15:08:17 +00:00
kuaifan
6c19dbc37a feat(app): 更新 resources/app 子模块(原生图片/视频预览改为类微信样式,支持下拉关闭)
同步 ai-kb 移动端手势文档的预览操作说明
2026-10-24 23:15:42 +00:00
kuaifan
b93099a3df fix(install): 生成 APP_KEY 后重启 php,避免 LaravelS 先于密钥启动导致全新安装后服务不可用 2026-10-22 11:19:18 +00:00
kuaifan
a2b3a0b30f release: v1.10.12 2026-10-22 09:21:53 +00:00
kuaifan
5d1b9332e1 chore(app): 更新 resources/app 子模块(迁移文档状态更新) 2026-10-22 09:13:13 +00:00
kuaifan
e68cb6f322 style(mobile): 统一项目与通讯录顶部团队按钮样式,加宽点击区域,项目选择团队后图标高亮 2026-10-22 09:02:03 +00:00
kuaifan
02ebbc1c96 feat(dashboard): 移动端仪表盘启用沉浸状态栏,滚动内容可穿过状态栏 2026-10-22 08:57:00 +00:00
kuaifan
571417f689 style(dashboard): 移动端截止时间右对齐,空值留空并隐藏整组为空的栏 2026-10-22 08:46:35 +00:00
kuaifan
2153879e09 fix(manticore): 兜底同步命令输出重定向到 /dev/null,避免 shell_exec 阻塞 task worker 导致串行执行 2026-10-22 08:40:12 +00:00
kuaifan
c0b1321479 fix(docker): PHP 镜像升级至 swoole-8.4.27 修复 CVE-2026-109618/109619 2026-10-22 05:53:26 +00:00
kuaifan
3edff276f3 style(dashboard): 移动端头部标题与右侧图标同行,副标题独占一行 2026-10-22 03:05:27 +00:00
kuaifan
a286490c9b chore(app): 更新 resources/app 子模块(进度记录 Metro 端口统一为 2220) 2026-10-21 18:47:06 +00:00
kuaifan
3192e43db3 feat(app): 新增 ./cmd expo dev 同时启动前端与 Metro,appexpo 命令更名为 expo
- ./cmd expo [build|dev|npm],与 ./cmd electron 同规范;CI 同步改用 ./cmd expo build
- expo dev:Metro 端口自动找空闲(指定 EXPO_PACKAGER_PROXY_URL 时固定并在占用时报错),地址按 VSCODE_PROXY_URI 推导或命令行传入
- vite 开发反代开启 gzip,减少手机经公网加载开发前端的体积
- resources/app:DooTask (dev) 变体
2026-10-21 18:41:54 +00:00
kuaifan
aca8fc6b24 refactor(dev): 开发模式改由 nginx 同源反代 vite,移除 VSCODE_PROXY_URI 拼接
- vite 开发 base 改为 /__vite/,nginx 通过 host.docker.internal 反代(含 HMR websocket),任意访问地址/代理下均可用
- vite 端口默认 5173,被占用自动顺延,实际端口由 devProxyPlugin 写入 nginx 片段并 reload(先 nginx -t 校验,失败不保留片段)
- HMR 客户端改连当前页面源;启动输出改为应用访问地址
- public/hot 只写 base/port,不再泄露整个 .env
- cmd dev 移除随机 APP_DEV_PORT、VSCODE_PROXY_URI 与 index.html 跳转;docker-compose nginx 增加 extra_hosts
2026-10-21 17:52:41 +00:00
kuaifan
c64fb1d98f refactor(system): 系统设置默认值改为数组统一填充 2026-10-20 13:58:32 +00:00
kuaifan
87feee20e6 fix(system): 系统设置缺少字段时回退默认值,避免 Undefined array key(修复 SystemBrandingTest) 2026-10-20 12:24:16 +00:00
kuaifan
8a324607e8 ci(app): 移除 App Internal 的 dev 临时触发 2026-10-20 12:04:47 +00:00
kuaifan
734642b817 ci(app): APK 签名指纹校验兼容新版 apksigner 输出格式 2026-10-20 11:17:24 +00:00
kuaifan
a9a655afda ci(app): 去掉 setup-android(runner 自带 Android SDK,默认安装已下架的 tools 包会失败) 2026-10-20 10:36:40 +00:00
kuaifan
6ff4ad9f6e ci(app): 临时触发同时覆盖 app-build.yml 变更 2026-10-20 10:33:04 +00:00
kuaifan
c32904de29 ci(app): 构建 App 前端需完整安装 electron(build.js 加载时依赖 electron 模块) 2026-10-20 10:32:22 +00:00
kuaifan
7fc142335c ci(app): 临时在 dev 触发 App Internal 验证出包 2026-10-20 10:29:41 +00:00
kuaifan
bad7634f27 ci(app): App 改由 Expo 壳构建发布,移除旧 eeui 子模块 resources/mobile
- 新增可复用工作流 app-build.yml(web 资源 / Android 正式签名 APK / iOS IPA)
- publish.yml 的 Android 改用 Expo 构建并上传 R2 与 Release
- ios-publish.yml 改为构建 Expo App 并上传 App Store Connect(构建号自动递增)
- app-internal.yml 改为调用 app-build.yml 出内测包
- 移除 ./cmd appbuild/eeui 与 electron/build.js 的 eeui 打包流程
2026-10-20 10:29:27 +00:00
kuaifan
a0e6d355f2 chore(app): 更新 Expo 壳子模块(厂商推送参数入库,正式签名改读 iCloud DooTaskKeys) 2026-10-20 10:07:26 +00:00
kuaifan
b4e242e9a4 chore(app): 更新 Expo 壳子模块(补齐荣耀/魅族厂商推送通道) 2026-10-20 08:10:47 +00:00
kuaifan
77a7231a61 chore(app): 更新 Expo 壳子模块(接入小米/OPPO/vivo 厂商推送通道) 2026-10-20 07:42:25 +00:00
kuaifan
d02c1d343a fix(push): DooPush 推送不再指定 OPPO IM 类别(未申请私信通道权限时整条被拒,错误 67) 2026-10-20 07:25:05 +00:00
kuaifan
e87ed82828 chore(app): 更新 Expo 壳子模块(升级 DooPush SDK 0.6.7,Android 角标改走厂商实现) 2026-10-20 03:35:46 +00:00
kuaifan
a74778f77e fix(push): 退出登录时系统配置模式的推送设备同步向 DooPush 解绑
users/logout 先按 token 哈希删除本地推送设备,App 随后的 remove 找不到记录而漏发解绑;
统一由 DeviceRegistry::forgetHash 清理并解绑,解绑请求超时缩短为 3 秒,不拖慢退出。
2026-10-19 16:22:32 +00:00
kuaifan
531129f48d feat(push): 移除友盟推送,APP 推送改为系统配置 / 自定义 / 关闭三档
- 删除友盟别名接口、模型、日志清理及 hedeqiang/umeng 依赖,迁移删除 umeng_alias / umeng_logs 表
- 系统配置(默认):通过官方 DooPush 共享通道自助注册租户,按 App 上报的推送 Token 绑定设备
- 自定义:沿用自有 DooPush 应用凭证;关闭:不推送也不记录设备
- 老站点升级后统一为系统配置,原自定义凭证保留
- PushUmengMsg 更名为 PushAppMsg;同步语言包、API 对照表与 ai-kb
2026-10-19 15:46:16 +00:00
kuaifan
9bdf23bec1 fix(app): 新版 App 冷启动后自动检查一次更新
旧 eeui 壳启动时由原生检查,新版壳只查询不弹窗,前端此前仅在每天回前台时检查,导致冷启动不提示
2026-10-19 15:10:05 +00:00
kuaifan
fe6adb2510 chore(app): 更新 Expo 壳子模块(修复接入 DooPush 后 Android Release 构建失败) 2026-10-19 15:04:00 +00:00
kuaifan
96bc70415f feat(app): 检查更新与桌面端一致,新版本不高于服务器版本才提示
- 提示逻辑移到 App.vue checkAppUpdate,$A.eeuiAppCheckUpdate 只做桥接
- 手动检查遇到服务器版本较低时提示等待服务器升级
- 更新语言包、事件对照表、ai-kb 与 Expo 壳子模块
2026-10-19 13:41:53 +00:00
kuaifan
bf1c9b7751 feat(app): 新版 App 检查更新提示与设置页手动检查
- $A.eeuiAppCheckUpdate 在 Expo 壳中弹出「发现新版本」,iOS 跳 App Store、Android 浏览器下载 apk
- 设置「版本」右侧新增「检查更新」
- 更新 Expo 壳子模块、语言包(含 DooPush 遗漏的两条后端文案)与 ai-kb
2026-10-19 13:15:20 +00:00
kuaifan
ef4bfd6ca4 chore(app): 更新 Expo 壳子模块(Release 包剥离 _expo._tcp) 2026-10-19 12:57:03 +00:00
kuaifan
bc8d769447 chore(app): 更新 Expo 壳子模块(iOS 权限说明多语言化、隐私清单) 2026-10-19 12:26:22 +00:00
kuaifan
a49ee608a5 feat(push): 新增 DooPush 推送通道,与友盟并存
- 新增 app_push_devices 表与 api/push/device 接口(新版 App 上报/删除 DooPush 设备)
- app/Module/AppPush 统一分发友盟与 DooPush,业务字段经 payload.data 下发
- 系统设置「App 推送」新增 DooPush 配置;点击通知打开对应会话
- 更新 Expo 壳子模块(接入 DooPush)、语言包与 ai-kb
2026-10-19 10:33:19 +00:00
kuaifan
0f2f165940 chore(app): 更新 Expo 壳子模块(iPad 窗口控件紧凑样式) 2026-10-19 08:43:29 +00:00
kuaifan
c1d844dbf4 chore(app): 更新 Expo 壳子模块(iOS 首次定位转圈修复、Android 系统定位、会议页安全区) 2026-10-19 02:54:19 +00:00
kuaifan
9727fe9ab2 chore(app): 更新 app 与 mobile 子模块(真机 UI 修复、会议扬声器图标) 2026-10-18 15:59:35 +00:00
kuaifan
ebb41bb73e chore(app): 更新 Expo 壳子模块(Android 正式签名与旧版覆盖升级适配) 2026-10-18 08:25:59 +00:00
kuaifan
7158eaa634 chore(app): 更新 Expo 壳子模块(iOS Release 验收记录) 2026-10-18 06:40:51 +00:00
kuaifan
9e1f446fe7 chore(app): 更新 Expo 壳子模块(修复 iOS Release 本地 Web 服务监听) 2026-10-18 05:24:59 +00:00
kuaifan
2c465d66ab feat(app): 页面兼容 P2(壳侧对齐 + 用户详情拨号分支修正) 2026-10-18 04:37:17 +00:00
kuaifan
e83c28de5d chore(app): 更新 app 子模块(页面兼容 P1 壳侧修复) 2026-10-18 03:10:27 +00:00
kuaifan
df66667f56 chore(app): 更新 Expo 壳子模块(iOS 键盘遮挡修复) 2026-10-18 00:44:02 +00:00
kuaifan
6a0cd0efd6 chore(app): 更新 Expo 壳子模块(页面级兼容:键盘、滚动锁、定位、录音权限) 2026-10-18 00:17:00 +00:00
kuaifan
945e5b10f2 feat(app): 新增 Expo 内测构建链路并更新 resources/app
- ./cmd appexpo [build] / appexpo npm:编译前端并同步到 resources/app/assets/web
- electron/build.js 新增 app-expo 目标,旧 eeui 流程不变
- 新增仅手动触发的 Expo App Internal workflow(APK / IPA artifact)
- resources/app 更新至 M3 会议与 M4 签名脚本
2026-10-17 23:08:24 +00:00
kuaifan
7ee2aed8d7 feat(app): 新增 Expo 移动端壳子模块 resources/app
一期替换 eeui(resources/mobile 暂保留作参考与旧版构建)。
2026-10-17 15:44:18 +00:00
231 changed files with 5475 additions and 2964 deletions

407
.github/workflows/app-build.yml vendored Normal file
View File

@ -0,0 +1,407 @@
name: "App Build"
# Expo App(resources/app 子模块)构建,供 publish.yml(Android 发版)、ios-publish.yml(App Store)
# 与 app-internal.yml(内测)调用;只产出 artifact(app-android / app-ios),上传发布由调用方负责。
#
# Secrets(仓库级,调用方 secrets: inherit):
# ANDROID_KEYSTORE_BASE64 / ANDROID_KEYSTORE_PASSWORD / ANDROID_KEY_ALIAS / ANDROID_KEY_PASSWORD Android 正式签名
# IOS_CERTIFICATE_BASE64 / IOS_CERTIFICATE_PASSWORD Apple Distribution 证书
# IOS_PROVISION_PROFILE_BASE64 / IOS_SHARE_EXTENSION_PROVISION_PROFILE_BASE64 App Store profiles
# IOS_INTERNAL_PROVISION_PROFILE_BASE64 / IOS_INTERNAL_SHARE_EXTENSION_PROVISION_PROFILE_BASE64 内测 profiles(可选)
# ASC_API_KEY_ID / ASC_ISSUER_ID / ASC_API_KEY_P8_BASE64 App Store 构建号查询
on:
workflow_call:
inputs:
platform:
description: all / android / ios
type: string
default: all
ios_export_method:
description: app-store / ad-hoc / development
type: string
default: ad-hoc
release:
description: 正式发布:签名缺失或不符时失败,不退化为 debug 签名或跳过
type: boolean
default: false
outputs:
version:
value: ${{ jobs.prepare.outputs.version }}
permissions:
contents: read
env:
CI: '1'
NODE_ENV: production
# 必须使用包内前端,禁止开发机 .env 覆盖 WebView 地址
EXPO_NO_DOTENV: '1'
EXPO_PUBLIC_WEB_URL: ''
# 旧版正式包(eeui)使用的签名证书;新包必须一致才能覆盖升级
ANDROID_CERT_SHA256: CE:30:13:6F:B9:28:56:13:C7:41:CB:CE:E3:6B:3C:DB:1F:01:D5:26:A7:3F:17:4E:1E:B2:B8:37:F1:E4:A2:B6
jobs:
prepare:
name: Prepare bundled web assets
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
version: ${{ steps.version.outputs.version }}
ios_ready: ${{ steps.signing.outputs.ios_ready }}
steps:
- uses: actions/checkout@v4
# 只取 Expo 子模块(drawio 体积大且与 App 无关),使用主仓库锁定的提交
- name: Checkout Expo submodule
run: git submodule update --init resources/app
- uses: actions/setup-node@v4
with:
# Expo 58 / RN 0.88 需要 Node ^22.13 / ^24.3 或更新版本
node-version: '24'
- name: Read version
id: version
run: node -e 'require("fs").appendFileSync(process.env.GITHUB_OUTPUT, `version=${require("./package.json").version}\n`)'
- name: Check iOS signing inputs
id: signing
env:
PLATFORM: ${{ inputs.platform }}
RELEASE: ${{ inputs.release }}
IOS_CERTIFICATE_BASE64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
APP_PROFILE: ${{ inputs.ios_export_method == 'app-store' && secrets.IOS_PROVISION_PROFILE_BASE64 || secrets.IOS_INTERNAL_PROVISION_PROFILE_BASE64 }}
SHARE_PROFILE: ${{ inputs.ios_export_method == 'app-store' && secrets.IOS_SHARE_EXTENSION_PROVISION_PROFILE_BASE64 || secrets.IOS_INTERNAL_SHARE_EXTENSION_PROVISION_PROFILE_BASE64 }}
run: |
if [ "$PLATFORM" = android ]; then
echo 'ios_ready=false' >> "$GITHUB_OUTPUT"
elif [ -n "$IOS_CERTIFICATE_BASE64" ] && [ -n "$APP_PROFILE" ] && [ -n "$SHARE_PROFILE" ]; then
echo 'ios_ready=true' >> "$GITHUB_OUTPUT"
elif [ "$RELEASE" = true ]; then
echo '::error::缺少 iOS 证书或 App Store profiles(IOS_CERTIFICATE_BASE64 / IOS_PROVISION_PROFILE_BASE64 / IOS_SHARE_EXTENSION_PROVISION_PROFILE_BASE64)'
exit 1
else
echo 'ios_ready=false' >> "$GITHUB_OUTPUT"
echo '::notice::跳过 iOS:缺少证书或内测 profiles(IOS_INTERNAL_PROVISION_PROFILE_BASE64 / IOS_INTERNAL_SHARE_EXTENSION_PROVISION_PROFILE_BASE64)'
fi
- name: Install frontend dependencies
# 主仓库没有追踪 npm lockfile;须显式包含 Vite 等 devDependencies
run: npm install --include=dev --no-package-lock --no-audit --no-fund
# electron/build.js 经 lib/utils.js 在加载时 require("electron"),需要完整安装(含二进制)
- name: Install build script dependencies
working-directory: electron
run: npm install --include=dev --no-package-lock --no-audit --no-fund
- name: Build and copy web assets
run: ./cmd expo build
- name: Upload bundled web assets
uses: actions/upload-artifact@v4
with:
name: app-web-assets
path: |
resources/app/assets/web/
resources/app/version.json
if-no-files-found: error
retention-days: 3
android:
name: Android APK
needs: prepare
if: inputs.platform != 'ios'
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- name: Free disk space
run: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/.ghcup /usr/local/share/boost /opt/hostedtoolcache/CodeQL
- uses: actions/checkout@v4
- name: Checkout Expo submodule
run: git submodule update --init resources/app
- uses: actions/download-artifact@v4
with:
name: app-web-assets
path: resources/app
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
cache-dependency-path: resources/app/package-lock.json
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Install Expo dependencies
working-directory: resources/app
run: npm ci --include=dev --no-audit --no-fund
- name: Prebuild Android
working-directory: resources/app
run: npx expo prebuild --platform android --clean --no-install
- name: Prepare Android keystore
id: signing
env:
RELEASE: ${{ inputs.release }}
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
if [ -n "$ANDROID_KEYSTORE_BASE64" ] && [ -n "$ANDROID_KEYSTORE_PASSWORD" ] && [ -n "$ANDROID_KEY_ALIAS" ] && [ -n "$ANDROID_KEY_PASSWORD" ]; then
umask 077
echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > "$RUNNER_TEMP/dootask-release.jks"
echo "ANDROID_KEYSTORE_PATH=$RUNNER_TEMP/dootask-release.jks" >> "$GITHUB_ENV"
echo 'mode=release' >> "$GITHUB_OUTPUT"
elif [ "$RELEASE" = true ]; then
echo '::error::缺少 Android 签名 secrets(ANDROID_KEYSTORE_BASE64 / ANDROID_KEYSTORE_PASSWORD / ANDROID_KEY_ALIAS / ANDROID_KEY_PASSWORD)'
exit 1
else
echo 'mode=debug' >> "$GITHUB_OUTPUT"
echo '::notice::Android 签名 secrets 不完整,使用 debug 签名(不能覆盖安装正式包)'
fi
- name: Assemble Release
working-directory: resources/app/android
env:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
# R8 混淆元空间占用大,默认 512m 会 OOM
run: ./gradlew :app:assembleRelease --no-daemon --init-script ../scripts/android-signing.gradle "-Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=2g"
- name: Rename and verify APK
env:
VERSION: ${{ needs.prepare.outputs.version }}
MODE: ${{ steps.signing.outputs.mode }}
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/apk"
# 文件名与旧版一致:官网下载索引与 App 检查更新按 android 平台取 .apk
apk="$RUNNER_TEMP/apk/DooTask-v${VERSION}-android.apk"
mv resources/app/android/app/build/outputs/apk/release/app-release.apk "$apk"
apksigner=$(ls "$ANDROID_HOME"/build-tools/*/apksigner | sort -V | tail -1)
"$apksigner" verify "$apk"
if [ "$MODE" = release ]; then
# 新版 apksigner 的行首可能是 "Signer #1" 或 "Signer (minSdkVersion=…)",只按字段名匹配
certs=$("$apksigner" verify --print-certs "$apk")
echo "$certs" | grep -i 'certificate SHA-256 digest' || true
actual=$(echo "$certs" | grep -i -m1 'certificate SHA-256 digest:' | sed 's/.*: *//' | tr -d ': ' | tr 'A-F' 'a-f')
expected=$(echo "$ANDROID_CERT_SHA256" | tr -d ':' | tr 'A-F' 'a-f')
if [ "$actual" != "$expected" ]; then
echo "::error::APK 签名证书与正式包不一致($actual),无法覆盖升级"
exit 1
fi
fi
ls -la "$RUNNER_TEMP/apk"
- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: app-android
path: ${{ runner.temp }}/apk/*.apk
if-no-files-found: error
retention-days: 14
- name: Remove Android keystore
if: always()
run: rm -f "$RUNNER_TEMP/dootask-release.jks"
ios:
name: iOS IPA
needs: prepare
if: needs.prepare.outputs.ios_ready == 'true'
runs-on: macos-26
timeout-minutes: 120
env:
IOS_EXPORT_METHOD: ${{ inputs.ios_export_method }}
steps:
- uses: actions/checkout@v4
- name: Checkout Expo submodule
run: git submodule update --init resources/app
- uses: actions/download-artifact@v4
with:
name: app-web-assets
path: resources/app
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
cache-dependency-path: resources/app/package-lock.json
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Resolve App Store build number
if: inputs.ios_export_method == 'app-store'
env:
ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_API_KEY_P8_BASE64: ${{ secrets.ASC_API_KEY_P8_BASE64 }}
run: |
set -euo pipefail
ruby <<'RUBY'
require 'base64'
require 'json'
require 'net/http'
require 'openssl'
require 'uri'
BUNDLE_ID = 'com.dootask.task'
def base64url(value)
Base64.urlsafe_encode64(value).delete('=')
end
def asc_token
private_key = OpenSSL::PKey.read(Base64.decode64(ENV.fetch('ASC_API_KEY_P8_BASE64')))
now = Time.now.to_i
header = { alg: 'ES256', kid: ENV.fetch('ASC_API_KEY_ID'), typ: 'JWT' }
payload = { iss: ENV.fetch('ASC_ISSUER_ID'), iat: now, exp: now + 20 * 60, aud: 'appstoreconnect-v1' }
unsigned = "#{base64url(header.to_json)}.#{base64url(payload.to_json)}"
# ES256 签名须为 r||s 各 32 字节,而非 DER
der = OpenSSL::ASN1.decode(private_key.sign('SHA256', unsigned))
signature = der.value.map { |integer| integer.value.to_s(2).rjust(32, "\0")[-32, 32] }.join
"#{unsigned}.#{base64url(signature)}"
end
def asc_get(path, params, token)
uri = URI::HTTPS.build(host: 'api.appstoreconnect.apple.com', path: path, query: URI.encode_www_form(params))
loop do
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{token}"
http.request(request)
end
abort "App Store Connect API request failed: #{response.code} #{response.body}" unless response.is_a?(Net::HTTPSuccess)
parsed = JSON.parse(response.body)
yield parsed
next_link = parsed.dig('links', 'next')
break unless next_link
uri = URI(next_link)
end
end
token = asc_token
app_id = nil
asc_get('/v1/apps', { 'filter[bundleId]' => BUNDLE_ID, 'limit' => 1 }, token) do |page|
app_id = page.fetch('data').first&.fetch('id')
end
abort "App Store Connect app not found for bundle id #{BUNDLE_ID}" unless app_id
versions = []
asc_get('/v1/builds', { 'filter[app]' => app_id, 'fields[builds]' => 'version', 'limit' => 200 }, token) do |page|
versions.concat(page.fetch('data').map { |build| build.dig('attributes', 'version').to_s })
end
max_build = versions.select { |v| v.match?(/\A\d+\z/) }.map(&:to_i).max || 0
code = JSON.parse(File.read('resources/app/version.json')).fetch('codeVerson').to_i
next_build = [max_build + 1, code].max
File.open(ENV.fetch('GITHUB_ENV'), 'a') { |file| file.puts "IOS_BUILD_NUMBER=#{next_build}" }
puts "App Store Connect 最大构建号 #{max_build},本次构建号 #{next_build}"
RUBY
- name: Install Expo dependencies
working-directory: resources/app
run: npm ci --include=dev --no-audit --no-fund
- name: Prebuild iOS
working-directory: resources/app
run: npx expo prebuild --platform ios --clean --no-install
- name: Install CocoaPods
working-directory: resources/app/ios
run: pod install
- name: Import signing certificate
env:
IOS_CERTIFICATE_BASE64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
run: |
set -euo pipefail
umask 077
SIGNING_DIR="$RUNNER_TEMP/dootask-ios-signing"
mkdir -p "$SIGNING_DIR"
KEYCHAIN_PATH="$SIGNING_DIR/signing.keychain-db"
KEYCHAIN_PASSWORD=$(openssl rand -hex 20)
security list-keychains -d user > "$SIGNING_DIR/original-keychains.txt"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
echo "$IOS_CERTIFICATE_BASE64" | base64 --decode > "$SIGNING_DIR/certificate.p12"
security import "$SIGNING_DIR/certificate.p12" -P "$IOS_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db
- name: Install and validate profiles
working-directory: resources/app
env:
IOS_PROVISION_PROFILE_BASE64: ${{ inputs.ios_export_method == 'app-store' && secrets.IOS_PROVISION_PROFILE_BASE64 || secrets.IOS_INTERNAL_PROVISION_PROFILE_BASE64 }}
IOS_SHARE_EXTENSION_PROVISION_PROFILE_BASE64: ${{ inputs.ios_export_method == 'app-store' && secrets.IOS_SHARE_EXTENSION_PROVISION_PROFILE_BASE64 || secrets.IOS_INTERNAL_SHARE_EXTENSION_PROVISION_PROFILE_BASE64 }}
run: python3 scripts/prepare-ios-signing.py "$RUNNER_TEMP/dootask-ios-signing"
- name: Configure product target signing via Expo mod
working-directory: resources/app
run: node scripts/configure-ios-signing.js "$RUNNER_TEMP/dootask-ios-signing/signing.json"
- name: Archive iOS Release
working-directory: resources/app
run: |
xcodebuild archive -quiet \
-workspace ios/DooTask.xcworkspace \
-scheme DooTask \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$RUNNER_TEMP/DooTask.xcarchive" \
-derivedDataPath "$RUNNER_TEMP/dootask-derived-data"
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/DooTask.xcarchive" \
-exportOptionsPlist "$RUNNER_TEMP/dootask-ios-signing/ExportOptions.plist" \
-exportPath "$RUNNER_TEMP/dootask-ipa"
- name: Upload IPA
uses: actions/upload-artifact@v4
with:
name: app-ios
path: ${{ runner.temp }}/dootask-ipa/*.ipa
if-no-files-found: error
retention-days: 14
- name: Remove signing material
if: always()
run: |
SIGNING_DIR="$RUNNER_TEMP/dootask-ios-signing"
if [ -f "$SIGNING_DIR/signing.json" ]; then
python3 - "$SIGNING_DIR/signing.json" <<'PY'
import json
from pathlib import Path
import sys
for filename in json.loads(Path(sys.argv[1]).read_text())["installed_profiles"]:
Path(filename).unlink(missing_ok=True)
PY
fi
if [ -f "$SIGNING_DIR/original-keychains.txt" ]; then
python3 - "$SIGNING_DIR/original-keychains.txt" <<'PY'
from pathlib import Path
import shlex
import subprocess
import sys
subprocess.run(["security", "list-keychains", "-d", "user", "-s", *shlex.split(Path(sys.argv[1]).read_text())], check=True)
PY
fi
security delete-keychain "$SIGNING_DIR/signing.keychain-db" 2>/dev/null || true
rm -rf "$SIGNING_DIR"

34
.github/workflows/app-internal.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: "App Internal"
# 内测出包:只产出 artifact,不创建 Release,也不上传 R2 / 商店。
# Android 使用正式签名(可覆盖安装正式包);iOS 需另配内测 profiles(含测试设备 UDID),未配置时跳过:
# IOS_INTERNAL_PROVISION_PROFILE_BASE64 / IOS_INTERNAL_SHARE_EXTENSION_PROVISION_PROFILE_BASE64
on:
workflow_dispatch:
inputs:
platform:
description: 构建平台
type: choice
options: [all, android, ios]
default: all
ios_export_method:
description: iOS 内测 profiles 类型(必须包含测试设备 UDID)
type: choice
options: [ad-hoc, development]
default: ad-hoc
permissions:
contents: read
concurrency:
group: app-internal-${{ github.ref }}
cancel-in-progress: false
jobs:
build:
name: Build
uses: ./.github/workflows/app-build.yml
with:
platform: ${{ inputs.platform || 'all' }}
ios_export_method: ${{ inputs.ios_export_method || 'ad-hoc' }}
secrets: inherit

View File

@ -1,5 +1,7 @@
name: "iOS Publish" name: "iOS Publish"
# 构建 Expo App 并上传 App Store Connect(之后在 App Store Connect 提交审核)。
#
# Required GitHub Secrets: # Required GitHub Secrets:
# #
# IOS_CERTIFICATE_BASE64 - Apple distribution certificate (.p12) encoded in base64 # IOS_CERTIFICATE_BASE64 - Apple distribution certificate (.p12) encoded in base64
@ -12,6 +14,11 @@ name: "iOS Publish"
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
upload:
description: 上传到 App Store Connect(关闭则只构建 IPA artifact)
type: boolean
default: true
permissions: permissions:
contents: read contents: read
@ -21,198 +28,34 @@ concurrency:
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
prepare-assets: build:
name: Prepare iOS Assets name: Build
runs-on: ubuntu-latest uses: ./.github/workflows/app-build.yml
timeout-minutes: 30
outputs:
version: ${{ steps.get-version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Get version from package.json
id: get-version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
- uses: actions/setup-node@v4
with: with:
node-version: 20 platform: ios
ios_export_method: app-store
release: true
secrets: inherit
- name: Install dependencies submit:
run: npm install name: Submit to App Store Connect
needs: build
- name: Install electron dependencies if: inputs.upload
run: |
pushd electron
npm install
popd
- name: Init mobile submodule
run: |
git submodule init
git submodule update --remote "resources/mobile"
- name: Build app assets
run: ./cmd appbuild publish
- name: Upload iOS platform artifacts
uses: actions/upload-artifact@v4
with:
name: ios-platform
path: resources/mobile/platforms/ios/
retention-days: 1
build-ios:
name: Build & Submit iOS
needs: prepare-assets
runs-on: macos-26 runs-on: macos-26
timeout-minutes: 60 timeout-minutes: 60
environment: build environment: build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/download-artifact@v4
- name: Init mobile submodule
run: |
git submodule init
git submodule update --remote "resources/mobile"
- name: Download prepared assets
uses: actions/download-artifact@v4
with: with:
name: ios-platform name: app-ios
path: resources/mobile/platforms/ios/ path: ipa-output
- name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1
uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: latest-stable xcode-version: latest-stable
- name: Install CocoaPods - name: Upload IPA
run: |
if [ -f "resources/mobile/platforms/ios/eeuiApp/Podfile" ]; then
cd resources/mobile/platforms/ios/eeuiApp
pod install
fi
- name: Import signing certificate
env:
IOS_CERTIFICATE_BASE64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
run: |
# Create temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -hex 20)
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Import certificate
CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
echo "$IOS_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
security import "$CERTIFICATE_PATH" \
-P "$IOS_CERTIFICATE_PASSWORD" \
-A \
-t cert \
-f pkcs12 \
-k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychain -d user -s "$KEYCHAIN_PATH"
- name: Import provisioning profile
env:
IOS_PROVISION_PROFILE_BASE64: ${{ secrets.IOS_PROVISION_PROFILE_BASE64 }}
IOS_SHARE_EXTENSION_PROVISION_PROFILE_BASE64: ${{ secrets.IOS_SHARE_EXTENSION_PROVISION_PROFILE_BASE64 }}
run: |
set -euo pipefail
APP_PROFILE_PATH=$RUNNER_TEMP/app.mobileprovision
SHARE_PROFILE_PATH=$RUNNER_TEMP/share-extension.mobileprovision
APP_PROFILE_PLIST=$RUNNER_TEMP/app-profile.plist
SHARE_PROFILE_PLIST=$RUNNER_TEMP/share-extension-profile.plist
echo "$IOS_PROVISION_PROFILE_BASE64" | base64 --decode > "$APP_PROFILE_PATH"
echo "$IOS_SHARE_EXTENSION_PROVISION_PROFILE_BASE64" | base64 --decode > "$SHARE_PROFILE_PATH"
security cms -D -i "$APP_PROFILE_PATH" > "$APP_PROFILE_PLIST"
security cms -D -i "$SHARE_PROFILE_PATH" > "$SHARE_PROFILE_PLIST"
APP_PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print :Name" "$APP_PROFILE_PLIST")
SHARE_PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print :Name" "$SHARE_PROFILE_PLIST")
IOS_TEAM_ID=$(/usr/libexec/PlistBuddy -c "Print :TeamIdentifier:0" "$APP_PROFILE_PLIST")
APP_PROFILE_APP_ID=$(/usr/libexec/PlistBuddy -c "Print :Entitlements:application-identifier" "$APP_PROFILE_PLIST")
SHARE_PROFILE_APP_ID=$(/usr/libexec/PlistBuddy -c "Print :Entitlements:application-identifier" "$SHARE_PROFILE_PLIST")
if [ "$APP_PROFILE_APP_ID" != "$IOS_TEAM_ID.com.dootask.task" ]; then
echo "Expected app profile for $IOS_TEAM_ID.com.dootask.task, got $APP_PROFILE_APP_ID"
exit 1
fi
if [ "$SHARE_PROFILE_APP_ID" != "$IOS_TEAM_ID.com.dootask.task.shareExtension" ]; then
echo "Expected share extension profile for $IOS_TEAM_ID.com.dootask.task.shareExtension, got $SHARE_PROFILE_APP_ID"
exit 1
fi
if ! /usr/libexec/PlistBuddy -c "Print :Entitlements:aps-environment" "$APP_PROFILE_PLIST" >/dev/null; then
echo "The DooTask app profile must include Push Notifications."
exit 1
fi
if ! /usr/libexec/PlistBuddy -c "Print :Entitlements:com.apple.security.application-groups" "$APP_PROFILE_PLIST" | grep -q "group.im.dootask"; then
echo "The DooTask app profile must include App Group group.im.dootask."
exit 1
fi
if ! /usr/libexec/PlistBuddy -c "Print :Entitlements:com.apple.security.application-groups" "$SHARE_PROFILE_PLIST" | grep -q "group.im.dootask"; then
echo "The share extension profile must include App Group group.im.dootask."
exit 1
fi
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp "$APP_PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/
cp "$SHARE_PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/
echo "APP_PROFILE_NAME=$APP_PROFILE_NAME" >> $GITHUB_ENV
echo "SHARE_PROFILE_NAME=$SHARE_PROFILE_NAME" >> $GITHUB_ENV
echo "IOS_TEAM_ID=$IOS_TEAM_ID" >> $GITHUB_ENV
- name: Configure manual signing
run: |
set -euo pipefail
ruby <<'RUBY'
require 'xcodeproj'
project_path = 'resources/mobile/platforms/ios/eeuiApp/eeuiApp.xcodeproj'
project = Xcodeproj::Project.open(project_path)
{
'DooTask' => ENV.fetch('APP_PROFILE_NAME'),
'ShareExtension' => ENV.fetch('SHARE_PROFILE_NAME')
}.each do |target_name, profile_name|
target = project.targets.find { |item| item.name == target_name }
abort "Target #{target_name} not found in #{project_path}" unless target
target.build_configurations.each do |config|
next unless config.name == 'Release'
config.build_settings['CODE_SIGN_STYLE'] = 'Manual'
config.build_settings['DEVELOPMENT_TEAM'] = ENV.fetch('IOS_TEAM_ID')
config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Distribution'
config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = profile_name
end
end
project.save
RUBY
- name: Resolve iOS build number
env: env:
ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }} ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
@ -220,196 +63,12 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
ruby <<'RUBY'
require 'base64'
require 'json'
require 'net/http'
require 'openssl'
require 'uri'
BUNDLE_ID = 'com.dootask.task'
VERSION_CONFIG_PATH = 'resources/mobile/platforms/ios/eeuiApp/Config/Version.xcconfig'
def base64url(value)
Base64.urlsafe_encode64(value).delete('=')
end
def jwt_es256_signature(private_key, unsigned)
der_signature = private_key.sign('SHA256', unsigned)
sequence = OpenSSL::ASN1.decode(der_signature)
sequence.value.map { |integer|
integer.value.to_s(2).rjust(32, "\0")[-32, 32]
}.join
end
def asc_token
key_id = ENV.fetch('ASC_API_KEY_ID')
issuer_id = ENV.fetch('ASC_ISSUER_ID')
private_key = OpenSSL::PKey.read(Base64.decode64(ENV.fetch('ASC_API_KEY_P8_BASE64')))
now = Time.now.to_i
header = { alg: 'ES256', kid: key_id, typ: 'JWT' }
payload = {
iss: issuer_id,
iat: now,
exp: now + 20 * 60,
aud: 'appstoreconnect-v1'
}
unsigned = "#{base64url(header.to_json)}.#{base64url(payload.to_json)}"
signature = jwt_es256_signature(private_key, unsigned)
"#{unsigned}.#{base64url(signature)}"
end
def asc_get(path, params, token)
uri = URI::HTTPS.build(
host: 'api.appstoreconnect.apple.com',
path: path,
query: URI.encode_www_form(params)
)
request_uri = uri
loop do
response = Net::HTTP.start(request_uri.host, request_uri.port, use_ssl: true) do |http|
request = Net::HTTP::Get.new(request_uri)
request['Authorization'] = "Bearer #{token}"
http.request(request)
end
unless response.is_a?(Net::HTTPSuccess)
abort "App Store Connect API request failed: #{response.code} #{response.body}"
end
parsed = JSON.parse(response.body)
yield parsed
next_link = parsed.dig('links', 'next')
break unless next_link
request_uri = URI(next_link)
end
end
token = asc_token
app_id = nil
asc_get('/v1/apps', { 'filter[bundleId]' => BUNDLE_ID, 'limit' => 1 }, token) do |page|
app_id = page.fetch('data').first&.fetch('id')
end
abort "App Store Connect app not found for bundle id #{BUNDLE_ID}" unless app_id
existing_versions = []
asc_get('/v1/builds', {
'filter[app]' => app_id,
'fields[builds]' => 'version',
'limit' => 200
}, token) do |page|
existing_versions.concat(
page.fetch('data').map { |build| build.dig('attributes', 'version').to_s }
)
end
max_build_number = existing_versions
.select { |version| version.match?(/\A\d+\z/) }
.map(&:to_i)
.max || 0
next_build_number = max_build_number + 1
config_content = File.exist?(VERSION_CONFIG_PATH) ? File.read(VERSION_CONFIG_PATH) : ''
if config_content.match?(/^VERSION_CODE\s*=/)
config_content = config_content.gsub(/^VERSION_CODE\s*=.*$/, "VERSION_CODE = #{next_build_number}")
else
config_content = "#{config_content.rstrip}\nVERSION_CODE = #{next_build_number}\n"
end
File.write(VERSION_CONFIG_PATH, config_content)
File.open(ENV.fetch('GITHUB_ENV'), 'a') { |file| file.puts "IOS_BUILD_NUMBER=#{next_build_number}" }
puts "Latest App Store Connect build number: #{max_build_number}"
puts "Resolved iOS build number: #{next_build_number}"
RUBY
- name: Build archive
run: |
set -euo pipefail
cd resources/mobile/platforms/ios/eeuiApp
xcodebuild archive \
-workspace eeuiApp.xcworkspace \
-scheme eeuiApp \
-configuration Release \
-destination "generic/platform=iOS" \
-archivePath $RUNNER_TEMP/eeuiApp.xcarchive \
-allowProvisioningUpdates \
DEVELOPMENT_TEAM=$IOS_TEAM_ID \
CODE_SIGN_IDENTITY="Apple Distribution" \
CODE_SIGN_STYLE=Manual \
| xcpretty
if [ ! -d "$RUNNER_TEMP/eeuiApp.xcarchive" ]; then
echo "Archive was not created at $RUNNER_TEMP/eeuiApp.xcarchive"
exit 1
fi
- name: Export IPA
run: |
set -euo pipefail
cd resources/mobile/platforms/ios/eeuiApp
# Generate ExportOptions.plist
cat > $RUNNER_TEMP/ExportOptions.plist << PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<key>signingStyle</key>
<string>manual</string>
<key>teamID</key>
<string>${IOS_TEAM_ID}</string>
<key>provisioningProfiles</key>
<dict>
<key>com.dootask.task</key>
<string>${APP_PROFILE_NAME}</string>
<key>com.dootask.task.shareExtension</key>
<string>${SHARE_PROFILE_NAME}</string>
</dict>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
PLIST
xcodebuild -exportArchive \
-archivePath $RUNNER_TEMP/eeuiApp.xcarchive \
-exportOptionsPlist $RUNNER_TEMP/ExportOptions.plist \
-exportPath $RUNNER_TEMP/ipa-output \
-allowProvisioningUpdates \
| xcpretty
- name: Submit to App Store Connect
env:
ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_API_KEY_P8_BASE64: ${{ secrets.ASC_API_KEY_P8_BASE64 }}
run: |
set -euo pipefail
# Prepare API key
mkdir -p ~/private_keys mkdir -p ~/private_keys
echo "$ASC_API_KEY_P8_BASE64" | base64 --decode > ~/private_keys/AuthKey_${ASC_API_KEY_ID}.p8 echo "$ASC_API_KEY_P8_BASE64" | base64 --decode > ~/private_keys/AuthKey_${ASC_API_KEY_ID}.p8
# Find and upload IPA IPA_PATH=$(find ipa-output -name "*.ipa" | head -1)
IPA_PATH=$(find $RUNNER_TEMP/ipa-output -name "*.ipa" | head -1)
if [ -z "$IPA_PATH" ]; then if [ -z "$IPA_PATH" ]; then
echo "No IPA file found in $RUNNER_TEMP/ipa-output" echo "No IPA file found in ipa-output"
exit 1 exit 1
fi fi
echo "Uploading: $IPA_PATH" echo "Uploading: $IPA_PATH"
@ -422,11 +81,4 @@ jobs:
- name: Clean up - name: Clean up
if: always() if: always()
run: | run: rm -rf ~/private_keys
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db 2>/dev/null || true
rm -f $RUNNER_TEMP/certificate.p12
rm -f $RUNNER_TEMP/app.mobileprovision
rm -f $RUNNER_TEMP/share-extension.mobileprovision
rm -f $RUNNER_TEMP/app-profile.plist
rm -f $RUNNER_TEMP/share-extension-profile.plist
rm -rf ~/private_keys

View File

@ -131,8 +131,6 @@ jobs:
include: include:
- platform: "macos-latest" - platform: "macos-latest"
build_type: "mac" build_type: "mac"
- platform: "ubuntu-latest"
build_type: "android"
- platform: "windows-latest" - platform: "windows-latest"
build_type: "windows" build_type: "windows"
@ -147,73 +145,6 @@ jobs:
with: with:
node-version: 20.x node-version: 20.x
# Android 构建步骤
- name: (Android) Build Js
if: matrix.build_type == 'android'
uses: nick-fields/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
command: |
git submodule init
git submodule update --remote "resources/mobile"
./cmd appbuild publish
- name: (Android) Setup JDK 11
if: matrix.build_type == 'android'
uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "11"
- name: (Android) Build App
if: matrix.build_type == 'android'
uses: nick-fields/retry@v2
with:
timeout_minutes: 20
max_attempts: 5
command: |
cd resources/mobile/platforms/android/eeuiApp
chmod +x ./gradlew
./gradlew assembleRelease --quiet
- name: (Android) Upload File
if: matrix.build_type == 'android'
env:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
R2_PUBLIC_URL: ${{ secrets.R2_PUBLIC_URL }}
run: |
node ./electron/build.js android-upload
- name: (Android) Upload Release
if: matrix.build_type == 'android'
uses: actions/github-script@v7
env:
RELEASE_ID: ${{ needs.create-release.outputs.release_id }}
with:
script: |
const fs = require('fs');
const path = require('path');
const globby = require('globby');
// 查找 APK 文件
const files = await globby('resources/mobile/platforms/android/eeuiApp/app/build/outputs/apk/release/*.apk');
for (const file of files) {
const data = await fs.promises.readFile(file);
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: process.env.RELEASE_ID,
name: path.basename(file),
data: data
});
}
# Mac 构建步骤 # Mac 构建步骤
- name: (Mac) Build Client - name: (Mac) Build Client
if: matrix.build_type == 'mac' if: matrix.build_type == 'mac'
@ -247,8 +178,75 @@ jobs:
run: | run: |
./cmd electron win ./cmd electron win
build-android:
needs: [ check-version, create-release ]
if: needs.check-version.outputs.should_release == 'true'
permissions:
contents: read
uses: ./.github/workflows/app-build.yml
with:
platform: android
release: true
secrets: inherit
upload-android:
needs: [ check-version, create-release, build-android ]
if: needs.check-version.outputs.should_release == 'true'
permissions:
contents: write
runs-on: ubuntu-latest
environment: build
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
- uses: actions/download-artifact@v4
with:
name: app-android
path: dist-android
- name: Upload File
env:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
R2_PUBLIC_URL: ${{ secrets.R2_PUBLIC_URL }}
run: |
pushd electron || exit
npm install
popd || exit
node ./electron/build.js android-upload dist-android
- name: Upload Release
uses: actions/github-script@v7
env:
RELEASE_ID: ${{ needs.create-release.outputs.release_id }}
with:
script: |
const fs = require('fs');
const path = require('path');
const files = fs.readdirSync('dist-android').filter(name => name.endsWith('.apk'));
if (!files.length) {
throw new Error('APK 未找到');
}
for (const name of files) {
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: process.env.RELEASE_ID,
name: name,
data: await fs.promises.readFile(path.join('dist-android', name))
});
}
publish-release: publish-release:
needs: [ check-version, create-release, pack-vendor, build-client ] needs: [ check-version, create-release, pack-vendor, build-client, upload-android ]
if: needs.check-version.outputs.should_release == 'true' && github.ref == 'refs/heads/pro' if: needs.check-version.outputs.should_release == 'true' && github.ref == 'refs/heads/pro'
permissions: permissions:
contents: write contents: write

View File

@ -66,7 +66,7 @@ jobs:
- name: Run PHPUnit in DooTask PHP image - name: Run PHPUnit in DooTask PHP image
run: | run: |
docker run --rm --network host -v "$GITHUB_WORKSPACE":/var/www -w /var/www \ docker run --rm --network host -v "$GITHUB_WORKSPACE":/var/www -w /var/www \
kuaifan/php:swoole-8.4 sh -c ' kuaifan/php:swoole-8.4.27 sh -c '
composer install --prefer-dist --no-interaction && composer install --prefer-dist --no-interaction &&
cp .env.example .env && cp .env.example .env &&
sed -i "s/^DB_HOST=.*/DB_HOST=127.0.0.1/" .env && sed -i "s/^DB_HOST=.*/DB_HOST=127.0.0.1/" .env &&

6
.gitmodules vendored
View File

@ -1,6 +1,6 @@
[submodule "resources/drawio"] [submodule "resources/drawio"]
path = resources/drawio path = resources/drawio
url = https://github.com/jgraph/drawio.git url = https://github.com/jgraph/drawio.git
[submodule "resources/mobile"] [submodule "resources/app"]
path = resources/mobile path = resources/app
url = https://github.com/kuaifan/dootask-app.git url = https://github.com/dootask/app.git

View File

@ -2,6 +2,73 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [1.10.38]
### Bug Fixes
- 修复 AI 助手浮动按钮有时点击没有反应的问题,点得稍慢一点也能正常打开。
- 修复在 AI 助手中切换场景后,当前对话没有保存的问题;切换前正在生成的回复也会完整保存下来。
### Performance
- 点击 AI 助手后立即打开,不再因为加载历史会话而卡顿。
### Miscellaneous
- AI 助手每个场景最多保留最近 200 个历史会话,更早的会自动清理。
## [1.10.35]
### Features
- 移动端设置页全新改版:顶部展示个人资料卡,设置项按类别分组并直接显示当前值,退出登录移到页面底部,查找和修改更方便。
- 手机 App 查看图片和视频改为类似微信的预览方式,下拉即可关闭。
- 消息长按菜单的复制功能简化为「复制」和「选择复制」,选择复制会打开全屏页面,方便只复制其中一部分文字。
- 手机 App 里切换项目列表与项目详情,以及从应用进入设置、日历、文件时,页面改为从右侧滑入、返回时滑出,操作更顺手。
- 切换到其他页面再返回时,会停留在之前浏览的位置,消息和通讯录分别记住各自的位置。
### Bug Fixes
- 修复小米手机收不到 App 推送,以及 App 切到后台时出现空白 DooTask 通知的问题。
- 修复 iOS App 无法连接通过公网 IP 或域名、以 http 方式访问的服务器的问题。
- 修复 iOS 上加载历史消息时,正在进行的滑动会被打断的问题。
- 修复 Safari 中加载失败的消息图片在滚动时反复重新加载的问题。
- 修复移动端搜索消息时,顶部搜索栏四角出现圆角的问题。
- 修复通讯录按部门查看时,长按联系人弹出菜单后仍会进入联系人详情的问题。
- 修复文件页的多处显示问题:触屏点击后残留的高亮效果、窄屏下搜索框与标题挨得太近,以及列表高度显示不正确。
- 修复全新安装后服务可能无法正常使用的问题。
### Performance
- 手机上打开会话和任务详情更流畅:先播放打开动画,同时显示占位内容,不再卡顿或出现空白。
- 消息列表滚动和长按弹出菜单更流畅,消息较多时也不会卡顿。
- 项目看板更流畅:一列里的内容变化不再影响其他列,列头菜单在首次打开时才加载。
## [1.10.12]
### Features
- 手机 App 全面升级为新版客户端:修复键盘遮挡输入框、定位一直转圈、会议页面显示不全等问题,iPad 窗口也更紧凑,整体使用更流畅稳定。
- App 推送全新升级:接入小米、OPPO、vivo、荣耀、魅族等手机厂商推送通道,App 在后台也能及时收到消息;点击通知可直接打开对应会话。
- 系统设置「App 推送」改为「系统配置 / 自定义 / 关闭」三种模式,默认的系统配置无需申请任何账号即可使用,原先的友盟推送不再使用。
- App 支持检查更新:启动时自动检查,设置页「版本」旁新增「检查更新」按钮,发现新版本时 iOS 跳转 App Store、Android 直接下载安装包。
- 移动端仪表盘界面优化:状态栏沉浸显示,标题与图标同排,截止时间右对齐,没有内容的栏目自动隐藏,页面更清爽。
- 移动端项目与通讯录顶部的团队按钮样式统一,点击区域更大,选择团队后图标高亮提示。
### Bug Fixes
- 修复退出登录后手机仍可能收到推送通知的问题。
- 修复部分 OPPO 手机收不到 App 推送的问题。
- 修复部分系统设置项缺失时读取出错的问题,缺失项会自动使用默认值。
### Performance
- 优化全文搜索的后台数据同步,不再占用后台任务通道,其他后台任务处理更及时。
### Security
- 升级 PHP 运行环境至 8.4.27,修复近期公开的两个高危远程代码执行漏洞(CVE-2026-109618、CVE-2026-109619),建议所有自部署用户尽快更新。
## [1.9.66] ## [1.9.66]
### Features ### Features

View File

@ -22,10 +22,17 @@ npm run build # 编译前端
- 如果想手动生成客户端执行 `./cmd electron` 根据提示选择操作。 - 如果想手动生成客户端执行 `./cmd electron` 根据提示选择操作。
## 编译 App ## App(Expo,`resources/app` 子模块)
```shell - **Android**:推送到 `pro` 时随 Publish 工作流自动构建(`.github/workflows/app-build.yml`),以正式签名出包,上传官网下载(R2)并附到 GitHub Release;App 内「检查更新」读取官网下载索引。
./cmd appbuild publish # 编译生成App需要的资源 - **iOS**:手动运行 **iOS Publish** 工作流(`gh workflow run ios-publish.yml --ref pro`),构建后上传 App Store Connect,再到 App Store Connect 提交审核。
``` - **内测包**:手动运行 **App Internal** 工作流,只产出 artifact。
- 本地出包见 `resources/app/scripts/mac-build.sh`(Android 正式签名读取构建机 iCloud 云盘 `DooTaskKeys/android/`)。
编译完后进入 `resources/mobile` EEUI框架目录内打包 Android 或 iOS 应用(Android 以实现 GitHub Actions 自动发布) | Secret | 用途 |
|---|---|
| `ANDROID_KEYSTORE_BASE64` / `ANDROID_KEYSTORE_PASSWORD` / `ANDROID_KEY_ALIAS` / `ANDROID_KEY_PASSWORD` | Android 正式签名(与旧版证书一致,CI 校验指纹) |
| `IOS_CERTIFICATE_BASE64` / `IOS_CERTIFICATE_PASSWORD` | Apple Distribution 证书 |
| `IOS_PROVISION_PROFILE_BASE64` / `IOS_SHARE_EXTENSION_PROVISION_PROFILE_BASE64` | App Store profiles(主 App 需含推送与 App Group `group.im.dootask`) |
| `ASC_API_KEY_ID` / `ASC_ISSUER_ID` / `ASC_API_KEY_P8_BASE64` | App Store Connect API(构建号查询与上传) |
| `IOS_INTERNAL_PROVISION_PROFILE_BASE64` / `IOS_INTERNAL_SHARE_EXTENSION_PROVISION_PROFILE_BASE64` | 可选,iOS 内测 profiles |

View File

@ -430,6 +430,7 @@ class AssistantController extends AbstractController
$sessions = AiAssistantSession::where('userid', $user->userid) $sessions = AiAssistantSession::where('userid', $user->userid)
->where('session_key', $sessionKey) ->where('session_key', $sessionKey)
->orderByDesc('updated_at') ->orderByDesc('updated_at')
->limit(20) // 与前端 maxSessionsPerKey 保持一致,避免全量返回拖慢打开
->get(); ->get();
$list = []; $list = [];
@ -512,6 +513,19 @@ class AssistantController extends AbstractController
], $session?->id); ], $session?->id);
$session->save(); $session->save();
// 每个场景最多保留 200 条会话,超出的按更新时间清理(含图片)
$staleSessions = AiAssistantSession::where('userid', $user->userid)
->where('session_key', $sessionKey)
->orderByDesc('updated_at')
->orderByDesc('id')
->skip(200)
->take(100)
->get();
foreach ($staleSessions as $stale) {
$this->deleteSessionImages($stale);
$stale->delete();
}
// 仅返回本次新增的图片URL // 仅返回本次新增的图片URL
$urls = []; $urls = [];
foreach ($newImageUrls as $imageId => $path) { foreach ($newImageUrls as $imageId => $path) {

View File

@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\InvokeController;
use App\Models\User;
use App\Module\AppPush\DeviceRegistry;
use App\Module\Base;
use Request;
/**
* @apiDefine push APP 推送
*/
class PushController extends InvokeController
{
/**
* @api {post} api/push/device 更新或移除 DooPush 设备
*
* @apiDescription 同时支持 GET 和 POST。update 需要有效 token 和 ios/android 平台,推送关闭时不记录;remove 仅按请求头 token 的 MD5 匹配解绑,允许 token 已失效。
* @apiVersion 1.0.0
* @apiGroup push
* @apiName device
*
* @apiParam {String="update","remove"} [action=update] 操作类型
* @apiParam {String} device_id DooPush 设备ID,1至64位纯数字字符串
* @apiParam {String="apns","fcm","hms","honor","xiaomi","oppo","vivo","meizu"} [vendor] update 时必填的厂商通道
* @apiParam {String} [push_token] 设备推送Token,系统配置模式下 update 必填(用于绑定到本站租户)
* @apiParam {String} [device_model] 设备型号,最多255个字符
* @apiParam {String} [app_version] 应用版本代码
* @apiParam {String} [app_version_name] 应用版本名称,与代码组合后最多100个字符
* @apiParam {String} [user_agent] 浏览器信息
* @apiParam {String} [is_notified] true、字符串true、1、字符串1表示有通知权限
*
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Array} data 返回数据(空数组)
*/
public function device()
{
$data = Request::input();
$action = $data['action'] ?? 'update';
$deviceId = $data['device_id'] ?? '';
if (!is_string($deviceId) || !preg_match('/^[0-9]{1,64}$/D', $deviceId)) {
return Base::retError('推送设备ID必须为1至64位数字');
}
if (!in_array($action, ['update', 'remove'], true)) {
return Base::retError('参数错误');
}
if ($action === 'remove') {
DeviceRegistry::remove($deviceId, (string)Request::header('token', ''));
return Base::retSuccess('删除成功');
}
$user = User::auth();
$platform = Base::platform();
if (!in_array($platform, ['ios', 'android'], true)) {
return Base::retError('设备类型错误');
}
if (!in_array($data['vendor'] ?? '', ['apns', 'fcm', 'hms', 'honor', 'xiaomi', 'oppo', 'vivo', 'meizu'], true)) {
return Base::retError('推送厂商错误');
}
foreach (['push_token' => 500, 'device_model' => 255, 'app_version' => 100, 'app_version_name' => 100, 'user_agent' => 65535] as $field => $limit) {
if (isset($data[$field]) && (!is_string($data[$field]) || mb_strlen($data[$field]) > $limit)) {
return Base::retError('参数错误');
}
}
$version = empty($data['app_version']) ? '' : (($data['app_version_name'] ?? '') . " ({$data['app_version']})");
if (mb_strlen($version) > 100 || strlen($data['user_agent'] ?? '') > 65535) {
return Base::retError('参数错误');
}
DeviceRegistry::update($user->userid, [
'device_id' => $deviceId,
'vendor' => $data['vendor'],
'platform' => $platform,
'device' => $data['device_model'] ?? '',
'version' => $version,
'ua' => $data['user_agent'] ?? '',
'is_notified' => in_array($data['is_notified'] ?? false, [true, 'true', 1, '1'], true) ? 1 : 0,
], $data['push_token'] ?? '');
return Base::retSuccess('保存成功');
}
}

View File

@ -13,6 +13,7 @@ use Response;
use Carbon\Carbon; use Carbon\Carbon;
use App\Module\Doo; use App\Module\Doo;
use App\Models\User; use App\Models\User;
use App\Module\AppPush\AppPush;
use App\Module\Base; use App\Module\Base;
use App\Module\OnlineLicense; use App\Module\OnlineLicense;
use App\Module\SystemUpdate; use App\Module\SystemUpdate;
@ -141,39 +142,45 @@ class SystemController extends AbstractController
// //
if ($type == 'all' || $type == 'save') { if ($type == 'all' || $type == 'save') {
User::auth('admin'); User::auth('admin');
$setting['reg_invite'] = $setting['reg_invite'] ?: Base::generatePassword(); $setting['reg_invite'] = ($setting['reg_invite'] ?? '') ?: Base::generatePassword();
} else { } else {
if (isset($setting['reg_invite'])) unset($setting['reg_invite']); if (isset($setting['reg_invite'])) unset($setting['reg_invite']);
} }
// //
// 未设置或为空的字段使用默认值
$defaults = [
'reg' => 'open',
'reg_identity' => 'normal',
'temp_account_alias' => '',
'login_code' => 'auto',
'password_policy' => 'simple',
'project_invite' => 'open',
'chat_information' => 'optional',
'anon_message' => 'open',
'convert_video' => 'close',
'compress_video' => 'close',
'e2e_message' => 'close',
'msg_rev_limit' => '',
'msg_edit_limit' => '',
'auto_archived' => 'close',
'task_visible' => 'close',
'all_group_mute' => 'open',
'todo_set_permission' => 'open',
'all_group_autoin' => 'yes',
'user_private_chat_mute' => 'open',
'user_group_chat_mute' => 'open',
'file_upload_limit' => '',
'unclaimed_task_reminder' => 'close',
'unclaimed_task_reminder_time' => '',
'task_ai_auto_analyze' => 'open',
'department_owner_project_view' => 'open',
];
foreach ($defaults as $key => $default) {
$setting[$key] = ($setting[$key] ?? '') ?: $default;
}
$setting['system_alias'] = ($setting['system_alias'] ?? '') ?: config('app.name'); $setting['system_alias'] = ($setting['system_alias'] ?? '') ?: config('app.name');
$setting['login_logo'] = Base::fillUrl($setting['login_logo'] ?? ''); $setting['login_logo'] = Base::fillUrl($setting['login_logo'] ?? '');
$setting['reg'] = $setting['reg'] ?: 'open'; $setting['archived_day'] = floatval($setting['archived_day'] ?? 0) ?: 7;
$setting['reg_identity'] = $setting['reg_identity'] ?: 'normal';
$setting['temp_account_alias'] = $setting['temp_account_alias'] ?: '';
$setting['login_code'] = $setting['login_code'] ?: 'auto';
$setting['password_policy'] = $setting['password_policy'] ?: 'simple';
$setting['project_invite'] = $setting['project_invite'] ?: 'open';
$setting['chat_information'] = $setting['chat_information'] ?: 'optional';
$setting['anon_message'] = $setting['anon_message'] ?: 'open';
$setting['convert_video'] = $setting['convert_video'] ?: 'close';
$setting['compress_video'] = $setting['compress_video'] ?: 'close';
$setting['e2e_message'] = $setting['e2e_message'] ?: 'close';
$setting['msg_rev_limit'] = $setting['msg_rev_limit'] ?: '';
$setting['msg_edit_limit'] = $setting['msg_edit_limit'] ?: '';
$setting['auto_archived'] = $setting['auto_archived'] ?: 'close';
$setting['archived_day'] = floatval($setting['archived_day']) ?: 7;
$setting['task_visible'] = $setting['task_visible'] ?: 'close';
$setting['all_group_mute'] = $setting['all_group_mute'] ?: 'open';
$setting['todo_set_permission'] = $setting['todo_set_permission'] ?: 'open';
$setting['all_group_autoin'] = $setting['all_group_autoin'] ?: 'yes';
$setting['user_private_chat_mute'] = $setting['user_private_chat_mute'] ?: 'open';
$setting['user_group_chat_mute'] = $setting['user_group_chat_mute'] ?: 'open';
$setting['file_upload_limit'] = $setting['file_upload_limit'] ?: '';
$setting['unclaimed_task_reminder'] = $setting['unclaimed_task_reminder'] ?: 'close';
$setting['unclaimed_task_reminder_time'] = $setting['unclaimed_task_reminder_time'] ?: '';
$setting['task_ai_auto_analyze'] = $setting['task_ai_auto_analyze'] ?: 'open';
$setting['department_owner_project_view'] = $setting['department_owner_project_view'] ?: 'open';
$setting = ProjectTaskHandoffSettings::normalize($setting); $setting = ProjectTaskHandoffSettings::normalize($setting);
$setting['app_ai_hidden'] = config('dootask.app_ai_hidden'); $setting['app_ai_hidden'] = config('dootask.app_ai_hidden');
$setting['server_timezone'] = config('app.timezone'); $setting['server_timezone'] = config('app.timezone');
@ -560,7 +567,7 @@ class SystemController extends AbstractController
* *
* @apiParam {String} type * @apiParam {String} type
* - get: 获取(默认) * - get: 获取(默认)
* - save: 保存设置(参数:['push', 'ios_key', 'ios_secret', 'android_key', 'android_secret']) * - save: 保存设置(参数:['doopush'(system 系统配置、custom 自定义、close 关闭), 'doopush_app_id', 'doopush_app_secret', 'doopush_base_url'])
* @apiSuccess {Number} ret 返回状态码(1正确、0错误) * @apiSuccess {Number} ret 返回状态码(1正确、0错误)
* @apiSuccess {String} msg 返回信息(错误描述) * @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据 * @apiSuccess {Object} data 返回数据
@ -577,21 +584,26 @@ class SystemController extends AbstractController
$all = Request::input(); $all = Request::input();
foreach ($all as $key => $value) { foreach ($all as $key => $value) {
if (!in_array($key, [ if (!in_array($key, [
'push', 'doopush',
'ios_key', 'doopush_app_id',
'ios_secret', 'doopush_app_secret',
'android_key', 'doopush_base_url',
'android_secret',
])) { ])) {
unset($all[$key]); unset($all[$key]);
} }
} }
if (!in_array($all['doopush'] ?? '', ['system', 'custom', 'close'], true)) {
return Base::retError('推送模式错误');
}
$setting = Base::setting('appPushSetting', Base::newTrim($all)); $setting = Base::setting('appPushSetting', Base::newTrim($all));
} else { } else {
$setting = Base::setting('appPushSetting'); $setting = Base::setting('appPushSetting');
} }
// //
$setting['push'] = $setting['push'] ?: 'close'; $setting['doopush'] = AppPush::mode();
$setting['doopush_app_id'] = $setting['doopush_app_id'] ?? '';
$setting['doopush_app_secret'] = $setting['doopush_app_secret'] ?? '';
$setting['doopush_base_url'] = $setting['doopush_base_url'] ?? '';
// //
return Base::retSuccess($type == 'save' ? '保存成功' : 'success', $setting ?: json_decode('{}')); return Base::retSuccess($type == 'save' ? '保存成功' : 'success', $setting ?: json_decode('{}'));
} }

View File

@ -19,7 +19,6 @@ use App\Models\ProjectTask;
use App\Models\ProjectTaskFile; use App\Models\ProjectTaskFile;
use App\Models\UserBot; use App\Models\UserBot;
use App\Models\WebSocket; use App\Models\WebSocket;
use App\Models\UmengAlias;
use App\Models\UserDelete; use App\Models\UserDelete;
use App\Models\UserDevice; use App\Models\UserDevice;
use App\Models\UserTransfer; use App\Models\UserTransfer;
@ -1514,85 +1513,6 @@ class UsersController extends AbstractController
return Base::retSuccess('绑定邮箱成功'); return Base::retSuccess('绑定邮箱成功');
} }
/**
* @api {get} api/users/umeng/alias 设置友盟别名
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName umeng__alias
*
* @apiParam {String} action
* - update: 更新(默认)
* - remove: 删除
* @apiParam {String} alias 别名
* @apiParam {String} [userAgent] 浏览器信息
* @apiParam {String} [deviceModel] 设备型号
* @apiParam {String} [isNotified] 是否有通知权限(0不通知、1通知)
* @apiParam {Number} [isDebug] 是否调试(0不调试、1调试)
*
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function umeng__alias()
{
$data = Request::input();
// 判断是否调试
if (intval($data['isDebug'])) {
return Base::retError('调试模式下不允许使用');
}
// 表单验证
Base::validator($data, [
'alias.required' => '别名不能为空',
'alias.between:2,20' => '别名的长度在2-20个字符',
]);
//
if ($data['action'] === 'remove') {
if ($data['alias']) {
UmengAlias::whereAlias($data['alias'])->delete();
}
return Base::retSuccess('删除成功');
}
//
if (!in_array(Base::platform(), ['ios', 'android'])) {
return Base::retError('设备类型错误');
}
//
$user = User::auth();
$inArray = [
'userid' => $user->userid,
'alias' => $data['alias'],
'platform' => Base::platform(),
];
$version = $data['appVersion'] ? ($data['appVersionName'] . " ({$data['appVersion']})") : '';
$isNotified = trim($data['isNotified']) === 'true' || $data['isNotified'] === true ? 1 : intval($data['isNotified']);
$row = UmengAlias::where($inArray);
if ($row->exists()) {
$row->update([
'ua' => $data['userAgent'],
'device' => $data['deviceModel'],
'device_hash' => UserDevice::check(),
'version' => $version,
'is_notified' => $isNotified,
'updated_at' => Carbon::now()
]);
return Base::retSuccess('别名已存在');
}
$row = UmengAlias::createInstance(array_merge($inArray, [
'ua' => $data['userAgent'],
'device' => $data['deviceModel'],
'device_hash' => UserDevice::check(),
'version' => $version,
'is_notified' => $isNotified,
]));
if ($row->save()) {
return Base::retSuccess('添加成功');
} else {
return Base::retError('添加错误');
}
}
/** /**
* @api {get} api/users/meeting/open 【会议】创建会议、加入会议 * @api {get} api/users/meeting/open 【会议】创建会议、加入会议
* *

View File

@ -63,11 +63,8 @@ class IndexController extends InvokeController
if (file_exists($hotFile)) { if (file_exists($hotFile)) {
$array = Base::json2array(file_get_contents($hotFile)); $array = Base::json2array(file_get_contents($hotFile));
$style = null; $style = null;
$script = preg_replace("/^(\/\/(.*?))(:\d+)?\//i", "$1:" . $array['APP_DEV_PORT'] . "/", asset_main("resources/assets/js/app.js")); // 开发模式:vite 经 nginx 反代在同源的 base 路径下(见 vite.config.js devProxyPlugin)
$proxyUri = Base::liveEnv('VSCODE_PROXY_URI'); $script = asset_main(trim($array['base'] ?? '/__vite/', '/') . "/resources/assets/js/app.js");
if (is_string($proxyUri) && preg_match('/^https?:\/\//i', $proxyUri)) {
$script = preg_replace('/^(https?:\/\/|\/\/)[^\/]+/', rtrim($proxyUri, '/'), $script, 1);
}
} else { } else {
$array = Base::json2array(file_get_contents($manifestFile)); $array = Base::json2array(file_get_contents($manifestFile));
$style = asset_main($array['resources/assets/js/app.js']['css'][0]); $style = asset_main($array['resources/assets/js/app.js']['css'][0]);
@ -264,7 +261,6 @@ class IndexController extends InvokeController
Task::deliver(new DeleteTmpTask('file')); Task::deliver(new DeleteTmpTask('file'));
Task::deliver(new DeleteTmpTask('tmp_file', 24)); Task::deliver(new DeleteTmpTask('tmp_file', 24));
Task::deliver(new DeleteTmpTask('user_device', 24)); Task::deliver(new DeleteTmpTask('user_device', 24));
Task::deliver(new DeleteTmpTask('umeng_log', 24 * 3));
// 删除机器人消息 // 删除机器人消息
Task::deliver(new DeleteBotMsgTask()); Task::deliver(new DeleteBotMsgTask());
// 周期任务 // 周期任务

View File

@ -0,0 +1,48 @@
<?php
namespace App\Models;
/**
* App\Models\AppPushDevice
*
* @property int $id
* @property int $userid 用户ID
* @property string $channel 推送通道
* @property string $device_id 推送服务设备ID
* @property string $vendor 厂商通道
* @property string $platform 平台类型
* @property string $device_hash 登录TOKEN MD5,关联UserDevice
* @property string $device 设备型号
* @property string $version 应用版本号
* @property string|null $ua userAgent
* @property int $is_notified 通知权限
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice query()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereChannel($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereDevice($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereDeviceHash($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereDeviceId($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereIsNotified($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice wherePlatform($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereUa($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereVendor($value)
* @method static \Illuminate\Database\Eloquent\Builder|AppPushDevice whereVersion($value)
* @mixin \Eloquent
*/
class AppPushDevice extends AbstractModel
{
protected $table = 'app_push_devices';
}

View File

@ -1,280 +0,0 @@
<?php
namespace App\Models;
use App\Module\Base;
use Carbon\Carbon;
use Hedeqiang\UMeng\Android;
use Hedeqiang\UMeng\IOS;
/**
* App\Models\UmengAlias
*
* @property int $id
* @property int|null $userid 会员ID
* @property string|null $alias 别名
* @property string|null $platform 平台类型
* @property string|null $device 设备类型
* @property string|null $device_hash 设备哈希值,用于关联UserDevice表
* @property string|null $version 应用版本号
* @property string|null $ua userAgent
* @property int|null $is_notified 通知权限
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias query()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereAlias($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereDevice($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereDeviceHash($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereIsNotified($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias wherePlatform($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereUa($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereUserid($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereVersion($value)
* @mixin \Eloquent
*/
class UmengAlias extends AbstractModel
{
protected $table = 'umeng_alias';
private static $waitSend = [];
/**
* 推送消息
* @param $push
* @return void
*/
private static function sendTask($push = null)
{
if ($push) {
self::$waitSend[] = $push;
}
if (!self::$waitSend) {
return;
}
$first = array_shift(self::$waitSend);
if (empty($first)) {
return;
}
$instance = null;
$responsePayload = null;
try {
switch ($first['platform']) {
case 'ios':
$instance = new IOS($first['config']);
break;
case 'android':
$instance = new Android($first['config']);
break;
default:
return;
}
$responsePayload = $instance->send($first['data']);
} catch (\Exception $e) {
$responsePayload = [
'error' => $e->getMessage(),
];
$first['retry'] = intval($first['retry'] ?? 0) + 1;
if ($first['retry'] > 3) {
info("[PushMsg] fail: " . $e->getMessage());
} else {
info("[PushMsg] retry ({$first['retry']}): " . $e->getMessage());
self::$waitSend[] = $first;
}
} finally {
if ($instance !== null) {
UmengLog::create([
'request' => Base::array2json($first['data']),
'response' => Base::array2json($responsePayload),
]);
}
self::sendTask();
}
}
/**
* 推送内容处理
* @param $string
* @return string
*/
private static function specialCharacters($string)
{
return str_replace(["\r\n", "\r", "\n"], '', $string);
}
/**
* 获取推送配置
* @return array|false
*/
public static function getPushConfig()
{
$setting = Base::setting('appPushSetting');
if ($setting['push'] !== 'open') {
return false;
}
$config = [];
if ($setting['ios_key']) {
$config['iOS'] = [
'appKey' => $setting['ios_key'],
'appMasterSecret' => $setting['ios_secret'],
'production_mode' => true,
];
}
if ($setting['android_key']) {
$config['Android'] = [
'appKey' => $setting['android_key'],
'appMasterSecret' => $setting['android_secret'],
'production_mode' => true,
];
}
return $config;
}
/**
* 推送消息
* @param string $alias
* @param string $platform
* @param array $array [title, subtitle, body, description, extra, seconds, badge]
* @return void
*/
private static function pushMsgToAlias($alias, $platform, $array)
{
$config = self::getPushConfig();
if ($config === false) {
return;
}
//
$title = self::specialCharacters($array['title'] ?: ''); // 标题
$subtitle = self::specialCharacters($array['subtitle'] ?: ''); // 副标题(iOS)
$body = self::specialCharacters($array['body'] ?: ''); // 通知内容
$description = $array['description'] ?: 'no description'; // 描述
$extra = is_array($array['extra']) ? $array['extra'] : []; // 额外参数
$seconds = intval($array['seconds']) ?: 86400; // 有效时间(单位:秒)
$badge = intval($array['badge']) ?: 0; // 角标数
//
switch ($platform) {
case 'ios':
if (!isset($config['iOS'])) {
return;
}
self::sendTask([
'platform' => $platform,
'config' => $config,
'data' => [
'description' => $description,
'payload' => array_merge([
'aps' => [
'alert' => [
'title' => $title,
'subtitle' => $subtitle,
'body' => $body,
],
'sound' => 'default',
'badge' => $badge,
],
], $extra),
'type' => 'customizedcast',
'alias_type' => 'userid',
'alias' => $alias,
'policy' => [
'expire_time' => Carbon::now()->addSeconds($seconds)->toDateTimeString(),
],
]
]);
break;
case 'android':
if (!isset($config['Android'])) {
return;
}
self::sendTask([
'platform' => $platform,
'config' => $config,
'data' => [
'description' => $description,
'payload' => array_merge([
'display_type' => 'notification',
'body' => [
'ticker' => $title,
'text' => $body,
'title' => $title,
'after_open' => 'go_app',
'play_sound' => true,
'set_badge' => min(99, $badge),
],
], $extra),
'type' => 'customizedcast',
'alias_type' => 'userid',
'alias' => $alias,
'mipush' => true,
'mi_activity' => 'app.eeui.umeng.activity.MfrMessageActivity',
'policy' => [
'expire_time' => Carbon::now()->addSeconds($seconds)->toDateTimeString(),
],
'category' => 1,
'channel_properties' => [
'main_activity' => 'com.dootask.task.WelcomeActivity',
'oppo_channel_id' => 'dootask',
'vivo_category' => 'IM',
'huawei_channel_importance' => 'NORMAL',
'huawei_channel_category' => 'IM',
'channel_fcm' => 0,
],
'local_properties' => [
'importance' => 'IMPORTANCE_DEFAULT',
'category' => 'CATEGORY_MESSAGE',
]
]
]);
break;
}
}
/**
* 推送给指定会员
* @param array|int $userid
* @param array $array
* @return void
*/
public static function pushMsgToUserid($userid, $array)
{
$builder = self::select(['id', 'platform', 'alias', 'userid'])->where('updated_at', '>', Carbon::now()->subMonth());
if (is_array($userid)) {
$builder->whereIn('userid', $userid);
} elseif (Base::isNumber($userid)) {
$builder->whereUserid($userid);
}
$builder
->orderByDesc('updated_at')
->chunkById(100, function ($datas) use ($array) {
$uids = $datas->groupBy('userid');
foreach ($uids as $uid => $rows) {
$array['badge'] = WebSocketDialogMsgRead::whereUserid($uid)->whereSilence(0)->whereReadAt(null)->count();
$lists = $rows->take(5)->groupBy('platform'); // 每个会员最多推送5个别名
foreach ($lists as $platform => $list) {
$alias = $list->pluck('alias')->implode(',');
try {
self::pushMsgToAlias($alias, $platform, $array);
} catch (\Exception $e) {
info("[PushMsg] fail: " . $e->getMessage());
}
}
}
});
}
}

View File

@ -1,32 +0,0 @@
<?php
namespace App\Models;
/**
* App\Models\UmengLog
*
* @property int $id
* @property string|null $request 请求参数
* @property string|null $response 推送返回
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
* @method static \Illuminate\Database\Eloquent\Builder|UmengLog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UmengLog newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UmengLog query()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
* @method static \Illuminate\Database\Eloquent\Builder|UmengLog whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengLog whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengLog whereRequest($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengLog whereResponse($value)
* @method static \Illuminate\Database\Eloquent\Builder|UmengLog whereUpdatedAt($value)
* @mixin \Eloquent
*/
class UmengLog extends AbstractModel
{
protected $guarded = [];
}

View File

@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use App\Module\AppPush\DeviceRegistry;
use App\Module\Base; use App\Module\Base;
use App\Module\Doo; use App\Module\Doo;
use App\Module\Lock; use App\Module\Lock;
@ -306,7 +307,7 @@ class UserDevice extends AbstractModel
} }
if (isset($hash)) { if (isset($hash)) {
Cache::forget(self::ck($hash)); Cache::forget(self::ck($hash));
UmengAlias::whereDeviceHash($hash)->delete(); DeviceRegistry::forgetHash($hash);
} }
} }
} }

View File

@ -0,0 +1,50 @@
<?php
namespace App\Module\AppPush;
use App\Module\Base;
class AppPush
{
/** 系统配置:官方 DooPush 共享通道(租户),设备 ID 由租户绑定返回 */
public const CHANNEL_SYSTEM = 'doopush_system';
/** 自定义:管理员自行配置的 DooPush 应用 */
public const CHANNEL_CUSTOM = 'doopush';
/**
* 推送模式:system(系统配置,默认)、custom(自定义)、close(关闭)
*/
public static function mode(): string
{
$mode = Base::setting('appPushSetting')['doopush'] ?? '';
return match ($mode) {
'custom' => 'custom',
'close' => 'close',
default => 'system',
};
}
public static function channel(): ?string
{
return match (self::mode()) {
'system' => self::CHANNEL_SYSTEM,
'custom' => self::CHANNEL_CUSTOM,
default => null,
};
}
public static function enabled(): bool
{
return self::mode() !== 'close';
}
/**
* @param array|int $userids
* @param array $msg [title, subtitle, body, description, extra, seconds, badge]
*/
public static function pushToUsers(array|int $userids, array $msg): void
{
app(DooPushClient::class)->pushToUsers($userids, $msg);
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Module\AppPush;
use App\Exceptions\ApiException;
use App\Models\AppPushDevice;
use App\Models\UserDevice;
class DeviceRegistry
{
/**
* 绑定当前登录设备;系统配置下先按推送 Token 绑定到本站租户,以 DooPush 返回的设备ID为准。
*/
public static function update(int $userid, array $attributes, string $pushToken = ''): void
{
$hash = UserDevice::check();
if (!$hash) {
throw new ApiException('身份已失效,请重新登录', [], -1);
}
$channel = AppPush::channel();
if ($channel === null) {
return;
}
if ($channel === AppPush::CHANNEL_SYSTEM) {
if ($pushToken === '') {
throw new ApiException('推送Token不能为空');
}
$attributes['device_id'] = app(DooPushClient::class)->bindDevice($pushToken);
}
$device = AppPushDevice::createInstance(array_merge($attributes, [
'userid' => $userid,
'channel' => $channel,
'device_hash' => $hash,
]));
// 唯一键原子 upsert,设备切换帐号或并发上报时保留一条绑定。
AppPushDevice::upsert([$device->getAttributes()], ['channel', 'device_id'], [
'userid', 'vendor', 'platform', 'device_hash', 'device', 'version', 'ua', 'is_notified', 'updated_at',
]);
}
/**
* 退出时 token 可以已失效,但必须与绑定时的 token 相同。
*/
public static function remove(string $deviceId, string $token): void
{
if ($token === '') {
return;
}
// 与 UserDevice::check()/forget() 共用 TOKEN MD5 算法,不依赖 token 解码。
self::forgetHash(md5($token), $deviceId);
}
/**
* 按登录 token 哈希清理推送设备(退出登录、踢下线等),系统配置的设备同时解除租户绑定
*/
public static function forgetHash(string $hash, ?string $deviceId = null): void
{
$query = AppPushDevice::whereIn('channel', [AppPush::CHANNEL_SYSTEM, AppPush::CHANNEL_CUSTOM])->whereDeviceHash($hash)
->when($deviceId !== null, fn ($q) => $q->whereDeviceId($deviceId));
$systemIds = (clone $query)->whereChannel(AppPush::CHANNEL_SYSTEM)->pluck('device_id')->all();
$query->delete();
foreach ($systemIds as $id) {
app(DooPushClient::class)->unbindDevice($id);
}
}
}

View File

@ -0,0 +1,282 @@
<?php
namespace App\Module\AppPush;
use App\Exceptions\ApiException;
use App\Models\AppPushDevice;
use App\Models\Setting;
use App\Models\WebSocketDialogMsgRead;
use App\Module\Base;
use App\Module\Ihttp;
use Cache;
use Illuminate\Contracts\Cache\LockTimeoutException;
use Carbon\Carbon;
class DooPushClient
{
/** 系统配置的租户凭证(不经推送设置接口下发前端) */
private const TENANT_SETTING = 'appPushTenant';
public function pushToUsers(array|int $userids, array $msg): void
{
try {
$channel = AppPush::channel();
if ($channel === null) {
return;
}
$content = str_replace(["\r\n", "\r", "\n"], '', $msg['body'] ?? '');
$title = trim(str_replace(["\r\n", "\r", "\n"], '', $msg['title'] ?? ''));
$title = $title === '' ? trim($content) : $title;
if ($title === '' || trim($content) === '') {
return;
}
if ($channel === AppPush::CHANNEL_CUSTOM) {
$setting = Base::setting('appPushSetting');
$appId = trim((string)($setting['doopush_app_id'] ?? ''));
$secret = trim((string)($setting['doopush_app_secret'] ?? ''));
if ($appId === '' || $secret === '') {
return;
}
$baseUrl = rtrim(trim((string)($setting['doopush_base_url'] ?? '')), '/') ?: 'https://doopush.com/api/v1';
$url = $baseUrl . '/apps/' . rawurlencode($appId) . '/push';
} else {
$tenant = $this->tenant();
if (!$tenant) {
return;
}
$secret = $tenant['tenant_secret'];
$url = $this->systemUrl('/tenant/push');
}
$extra = [];
foreach (is_array($msg['extra'] ?? null) ? $msg['extra'] : [] as $key => $value) {
$extra[$key] = is_scalar($value) || $value === null ? (string)$value : Base::array2json($value);
}
// DooPush 只接受 action/url/data 与厂商参数,自定义字段须以 JSON 字符串放在 data(客户端解析)。
$payload = array_merge($extra ? ['data' => Base::array2json($extra)] : [], [
'huawei' => ['importance' => 'NORMAL', 'category' => 'IM'],
'honor' => ['importance' => 'NORMAL'],
// OPPO 的 IM 类别属私信通道,需在 OPPO 后台申请权限,否则整条推送被拒(错误 67),暂不指定。
// DooPush 的 vivo 通道使用 classification,而非 category。
'vivo' => ['classification' => 1],
]);
$headers = ['Authorization' => 'Bearer ' . $secret, 'Content-Type' => 'application/json'];
$userids = array_unique(array_map('intval', (array)$userids));
foreach ($userids as $userid) {
if ($userid <= 0) {
continue;
}
try {
// 按用户单独取最新5台,避免跨查询分块时重复发送或突破设备数限制。
$deviceIds = AppPushDevice::whereChannel($channel)->whereUserid($userid)
->where('updated_at', '>', Carbon::now()->subMonth())
->orderByDesc('updated_at')->orderByDesc('id')->limit(5)
->pluck('device_id')
// API 要求整数 ID,避免超长数字强转后意外指向另一台设备。
->map(fn ($id) => filter_var(ltrim($id, '0'), FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]))
->filter(fn ($id) => $id !== false)->values()->all();
if (!$deviceIds) {
continue;
}
$badge = WebSocketDialogMsgRead::whereUserid($userid)->whereSilence(0)->whereReadAt(null)->count();
$body = [
'title' => mb_substr($title, 0, 200),
'content' => $content,
'badge' => $badge,
'payload' => $payload,
];
if ($channel === AppPush::CHANNEL_CUSTOM) {
$this->send('POST', $url, $headers, $body + ['target' => ['type' => 'devices', 'device_ids' => $deviceIds]], $userid);
continue;
}
[$status, $result] = $this->send('POST', $url, $headers, $body + ['device_ids' => $deviceIds], $userid);
if ($status === 200) {
// 设备已被其他租户(其他服务器)绑定,本地不再推送。
$unbound = array_map('strval', (array)($result['data']['unbound_device_ids'] ?? []));
if ($unbound) {
AppPushDevice::whereChannel($channel)->whereIn('device_id', $unbound)->delete();
}
} elseif ($status === 401) {
// 租户已失效,下次绑定时重新注册;本轮不再继续请求。
$this->forgetTenant($secret);
return;
} elseif ($status === 403 || $status === 429) {
// 租户被禁用或超出今日配额,本轮其余用户同样会失败。
return;
}
} catch (\Throwable $e) {
info('[DooPush] user push failed: ' . $e->getMessage(), ['userid' => $userid]);
}
}
} catch (\Throwable $e) {
info('[DooPush] push failed: ' . $e->getMessage());
}
}
/**
* 系统配置:按推送 Token 将设备绑定到本站租户,返回 DooPush 设备ID
*/
public function bindDevice(string $pushToken): string
{
for ($attempt = 1; $attempt <= 2; $attempt++) {
$tenant = $this->tenant(true);
if (!$tenant) {
throw new ApiException('推送服务注册失败,请稍后再试');
}
[$status, $result] = $this->send('POST', $this->systemUrl('/tenant/devices'), [
'Authorization' => 'Bearer ' . $tenant['tenant_secret'],
'Content-Type' => 'application/json',
], ['token' => $pushToken]);
$deviceId = (string)($result['data']['device_id'] ?? '');
if ($status === 200 && preg_match('/^[1-9][0-9]{0,63}$/D', $deviceId)) {
return $deviceId;
}
if ($status === 401) {
// 租户已被删除或密钥失效:重新注册后再试一次。
$this->forgetTenant($tenant['tenant_secret']);
continue;
}
if ($status === 404) {
throw new ApiException('推送设备未注册,请稍后再试');
}
if ($status === 403) {
throw new ApiException('推送服务已被停用');
}
break;
}
throw new ApiException('推送设备绑定失败,请稍后再试');
}
/**
* 系统配置:解除设备与本站租户的绑定(失败不影响退出登录)
*/
public function unbindDevice(string $deviceId): void
{
try {
$tenant = $this->tenant(false);
if ($tenant) {
// 在退出登录请求中同步执行:只请求一次且缩短超时,网络异常时不拖慢退出。
$this->send('DELETE', $this->systemUrl('/tenant/devices/' . rawurlencode($deviceId)), [
'Authorization' => 'Bearer ' . $tenant['tenant_secret'],
], [], 0, 1, 3);
}
} catch (\Throwable $e) {
info('[DooPush] unbind failed: ' . $e->getMessage());
}
}
private function systemUrl(string $path): string
{
$config = config('dootask.doopush_system');
return rtrim($config['base_url'], '/') . '/apps/' . rawurlencode((string)$config['app_id']) . $path;
}
/**
* 读取租户凭证;切换官方地址或应用后视为未注册。$register 为 true 时按需自助注册。
*/
private function tenant(bool $register = false): ?array
{
$tenant = $this->storedTenant();
if ($tenant || !$register) {
return $tenant;
}
// 多进程并发绑定时只注册一次,其余进程等待后复用。
try {
return Cache::lock('doopush:tenant-register', 30)->block(15, function () {
return $this->storedTenant() ?: $this->registerTenant();
});
} catch (LockTimeoutException) {
return $this->storedTenant();
}
}
private function storedTenant(): ?array
{
// 直接读库:凭证可能刚被其他进程更新,不使用请求级缓存。
$tenant = Base::string2array(Setting::whereName(self::TENANT_SETTING)->value('setting'));
$config = config('dootask.doopush_system');
if (empty($tenant['tenant_secret'])
|| ($tenant['base_url'] ?? '') !== $config['base_url']
|| ($tenant['app_id'] ?? '') !== (string)$config['app_id']) {
return null;
}
return $tenant;
}
private function registerTenant(): ?array
{
$config = config('dootask.doopush_system');
[$status, $result] = $this->send('POST', $this->systemUrl('/tenants'), [
'X-App-Key' => $config['app_key'],
'Content-Type' => 'application/json',
], ['name' => 'DooTask', 'site_url' => mb_substr(Base::getSchemeAndHost(), 0, 255)]);
$secret = (string)($result['data']['tenant_secret'] ?? '');
if ($status !== 201 || $secret === '') {
info('[DooPush] tenant register failed', ['status' => $status, 'message' => $result['message'] ?? '']);
return null;
}
$tenant = [
'base_url' => $config['base_url'],
'app_id' => (string)$config['app_id'],
'tenant_id' => (int)($result['data']['tenant_id'] ?? 0),
'tenant_secret' => $secret,
];
Base::setting(self::TENANT_SETTING, $tenant);
return $tenant;
}
private function forgetTenant(string $secret): void
{
$tenant = $this->storedTenant();
// 仅清理失效的那份凭证,避免覆盖其他进程刚注册的新凭证。
if ($tenant && $tenant['tenant_secret'] === $secret) {
Base::setting(self::TENANT_SETTING, []);
}
}
/**
* 发送请求,网络错误或 5xx 时最多重试2次;返回 [HTTP 状态码(网络失败为 0), 响应 JSON]
*/
private function send(string $method, string $url, array $headers, array $body, int $userid = 0, int $attempts = 3, int $timeout = 10): array
{
$status = 0;
$result = [];
for ($attempt = 1; $attempt <= $attempts; $attempt++) {
try {
$response = $this->request($url, $headers, $body, $method, $timeout);
$result = Base::json2array($response['data'] ?? '');
$status = Base::isSuccess($response) ? (int)($response['msg'] ?? 0) : 0;
if ($status >= 200 && $status < 300 && in_array($result['code'] ?? 0, [200, 201], true)) {
return [$status, $result];
}
info('[DooPush] send failed', [
'userid' => $userid,
'attempt' => $attempt,
'status' => $status,
'message' => mb_substr((string)($result['message'] ?? $response['msg'] ?? 'Invalid response'), 0, 500),
]);
if ($status >= 400 && $status < 500 && isset($result['code'])) {
// 业务拒绝(鉴权、参数、配额等)重试无意义。
return [$status, $result];
}
} catch (\Throwable $e) {
$status = 0;
info('[DooPush] send failed: ' . $e->getMessage(), ['userid' => $userid, 'attempt' => $attempt]);
}
}
// 2xx 但响应无效时按网络失败处理。
return [$status >= 200 && $status < 300 ? 0 : $status, $result];
}
protected function request(string $url, array $headers, array $body, string $method = 'POST', int $timeout = 10): array
{
$post = $method === 'POST' ? json_encode($body, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) : '';
$options = [
'CURLOPT_SSL_VERIFYPEER' => true,
'CURLOPT_SSL_VERIFYHOST' => 2,
];
if ($method !== 'POST') {
$options['CURLOPT_CUSTOMREQUEST'] = $method;
}
return Ihttp::ihttp_request($url, $post, array_merge($headers, $options), $timeout);
}
}

View File

@ -3,7 +3,7 @@
namespace App\Tasks; namespace App\Tasks;
use App\Models\ProjectTask; use App\Models\ProjectTask;
use App\Module\Base; use App\Module\AppPush\AppPush;
use Carbon\Carbon; use Carbon\Carbon;
class AppPushTask extends AbstractTask class AppPushTask extends AbstractTask
@ -15,8 +15,7 @@ class AppPushTask extends AbstractTask
public function start() public function start()
{ {
$setting = Base::setting('appPushSetting'); if (!AppPush::enabled()) {
if ($setting['push'] !== 'open') {
return; return;
} }
ProjectTask::whereNull("complete_at") ProjectTask::whereNull("complete_at")

View File

@ -6,7 +6,6 @@ use App\Models\File;
use App\Models\TaskWorker; use App\Models\TaskWorker;
use App\Models\Tmp; use App\Models\Tmp;
use App\Models\UserDevice; use App\Models\UserDevice;
use App\Models\UmengLog;
use App\Models\WebSocketTmpMsg; use App\Models\WebSocketTmpMsg;
use App\Module\Base; use App\Module\Base;
use Carbon\Carbon; use Carbon\Carbon;
@ -125,17 +124,6 @@ class DeleteTmpTask extends AbstractTask
} }
}); });
break; break;
case 'umeng_log':
UmengLog::where('created_at', '<', Carbon::now()->subHours($this->hours))
->orderBy('id')
->chunk(500, function ($logs) {
/** @var UmengLog $log */
foreach ($logs as $log) {
$log->delete();
}
});
break;
} }
} }

View File

@ -248,14 +248,14 @@ class ManticoreSyncTask extends AbstractTask
private function runIncrementalSync(): void private function runIncrementalSync(): void
{ {
// 启动各类型的增量同步命令 // 启动各类型的增量同步命令
@shell_exec("php /var/www/artisan manticore:sync-files --i 2>&1 &"); @shell_exec("php /var/www/artisan manticore:sync-files --i > /dev/null 2>&1 &");
@shell_exec("php /var/www/artisan manticore:sync-users --i 2>&1 &"); @shell_exec("php /var/www/artisan manticore:sync-users --i > /dev/null 2>&1 &");
@shell_exec("php /var/www/artisan manticore:sync-projects --i 2>&1 &"); @shell_exec("php /var/www/artisan manticore:sync-projects --i > /dev/null 2>&1 &");
@shell_exec("php /var/www/artisan manticore:sync-tasks --i 2>&1 &"); @shell_exec("php /var/www/artisan manticore:sync-tasks --i > /dev/null 2>&1 &");
@shell_exec("php /var/www/artisan manticore:sync-msgs --i 2>&1 &"); @shell_exec("php /var/www/artisan manticore:sync-msgs --i > /dev/null 2>&1 &");
// 启动失败重试命令 // 启动失败重试命令
@shell_exec("php /var/www/artisan manticore:retry-failures 2>&1 &"); @shell_exec("php /var/www/artisan manticore:retry-failures > /dev/null 2>&1 &");
} }
public function end() public function end()

View File

@ -1,16 +1,15 @@
<?php <?php
namespace App\Tasks; namespace App\Tasks;
use App\Models\UmengAlias;
use App\Models\WebSocketDialogMsgRead; use App\Models\WebSocketDialogMsgRead;
use App\Module\Base; use App\Module\AppPush\AppPush;
use Cache; use Cache;
use Hhxsv5\LaravelS\Swoole\Task\Task; use Hhxsv5\LaravelS\Swoole\Task\Task;
/** /**
* 推送友盟消息 * 推送 APP 消息(DooPush)
*/ */
class PushUmengMsg extends AbstractTask class PushAppMsg extends AbstractTask
{ {
protected $userid = 0; protected $userid = 0;
protected $array = []; protected $array = [];
@ -32,8 +31,7 @@ class PushUmengMsg extends AbstractTask
if (empty($this->userid) || empty($this->array)) { if (empty($this->userid) || empty($this->array)) {
return; return;
} }
$setting = Base::setting('appPushSetting'); if (!AppPush::enabled()) {
if ($setting['push'] !== 'open') {
return; return;
} }
@ -77,7 +75,7 @@ class PushUmengMsg extends AbstractTask
// 直接推送 // 直接推送
if ($directPushUsers) { if ($directPushUsers) {
UmengAlias::pushMsgToUserid($directPushUsers, $this->array); AppPush::pushToUsers($directPushUsers, $this->array);
} }
// 创建延迟推送任务 // 创建延迟推送任务
@ -95,7 +93,7 @@ class PushUmengMsg extends AbstractTask
return; return;
} }
foreach ($this->endPush as $item) { foreach ($this->endPush as $item) {
$task = new PushUmengMsg($item['userid'], $item['array']); $task = new PushAppMsg($item['userid'], $item['array']);
$task->delay(10); $task->delay(10);
Task::deliver($task); Task::deliver($task);
} }

View File

@ -6,6 +6,7 @@ use App\Models\User;
use App\Models\WebSocketDialog; use App\Models\WebSocketDialog;
use App\Models\WebSocketDialogMsg; use App\Models\WebSocketDialogMsg;
use App\Models\WebSocketDialogMsgRead; use App\Models\WebSocketDialogMsgRead;
use App\Module\AppPush\AppPush;
use App\Module\Base; use App\Module\Base;
use App\Module\Doo; use App\Module\Doo;
use App\Services\RequestContext; use App\Services\RequestContext;
@ -164,7 +165,7 @@ class WebSocketDialogMsgTask extends AbstractTask
]); ]);
} }
// 开始推送消息 // 开始推送消息
$umengUserid = []; $appUserid = [];
foreach ($array as $item) { foreach ($array as $item) {
$this->endPush[] = [ $this->endPush[] = [
'userid' => $item['userid'], 'userid' => $item['userid'],
@ -182,26 +183,25 @@ class WebSocketDialogMsgTask extends AbstractTask
] ]
]; ];
if ($item['userid'] != $msg->userid && !$item['silence'] && !$this->silence) { if ($item['userid'] != $msg->userid && !$item['silence'] && !$this->silence) {
$umengUserid[] = $item['userid']; $appUserid[] = $item['userid'];
} }
} }
// umeng推送app // APP 推送(DooPush)
if ($umengUserid) { if ($appUserid) {
$setting = Base::setting('appPushSetting'); if (AppPush::enabled()) {
if ($setting['push'] === 'open') {
if ($msg->userid == -1) { if ($msg->userid == -1) {
// AI 助手虚拟用户没有会员记录,取自定义昵称或默认名称 // AI 助手虚拟用户没有会员记录,取自定义昵称或默认名称
$umengTitle = ($msg->msg['nickname'] ?? '') ?: Doo::translate('AI 助手'); $appTitle = ($msg->msg['nickname'] ?? '') ?: Doo::translate('AI 助手');
} else { } else {
$umengTitle = User::userid2nickname($msg->userid); $appTitle = User::userid2nickname($msg->userid);
} }
$umengBody = WebSocketDialogMsg::previewMsg($msg); $appBody = WebSocketDialogMsg::previewMsg($msg);
if ($dialog->type == 'group') { if ($dialog->type == 'group') {
$umengBody = $umengTitle . ': ' . $umengBody; $appBody = $appTitle . ': ' . $appBody;
$umengTitle = $dialog->getGroupName(); $appTitle = $dialog->getGroupName();
} }
$langs = User::select(['userid', 'lang']) $langs = User::select(['userid', 'lang'])
->whereIn('userid', $umengUserid) ->whereIn('userid', $appUserid)
->whereDisableAt(null) ->whereDisableAt(null)
->get() ->get()
->groupBy('lang') ->groupBy('lang')
@ -210,9 +210,9 @@ class WebSocketDialogMsgTask extends AbstractTask
}); });
foreach ($langs as $lang => $uids) { foreach ($langs as $lang => $uids) {
Doo::setLanguage($lang); Doo::setLanguage($lang);
$umengMsg = [ $appMsg = [
'title' => $umengTitle, 'title' => $appTitle,
'body' => $umengBody, 'body' => $appBody,
'description' => "MID:{$msg->id}", 'description' => "MID:{$msg->id}",
'seconds' => 3600, 'seconds' => 3600,
'badge' => 1, 'badge' => 1,
@ -221,7 +221,7 @@ class WebSocketDialogMsgTask extends AbstractTask
'msg_id' => $msg->id, 'msg_id' => $msg->id,
] ]
]; ];
$this->endArray[] = new PushUmengMsg($uids->toArray(), $umengMsg); $this->endArray[] = new PushAppMsg($uids->toArray(), $appMsg);
} }
} }
} }

84
cmd
View File

@ -379,18 +379,6 @@ web_build() {
if [ ! -d "./node_modules" ]; then if [ ! -d "./node_modules" ]; then
npm install npm install
fi fi
if [ "$type" = "dev" ]; then
echo "<script>window.location.href=window.location.href.replace(/:\d+/, ':' + $(env_get APP_PORT))</script>" > ./index.html
if [ -z "$(env_get APP_DEV_PORT)" ]; then
env_set APP_DEV_PORT $(rand 20001 30000)
fi
if [ -n "${VSCODE_PROXY_URI:-}" ]; then
APP_REAL_URI=$(TARGET_PORT="$(env_get APP_PORT)" node -p "process.env.VSCODE_PROXY_URI.replace(/\{\{port\}\}/g, process.env.TARGET_PORT || '')")
VSCODE_PROXY_URI=$(APP_DEV_PORT="$(env_get APP_DEV_PORT)" node -p "process.env.VSCODE_PROXY_URI.replace(/\{\{port\}\}/g, process.env.APP_DEV_PORT || '')")
echo "<script>window.location.href='${APP_REAL_URI}'</script>" > ./index.html
fi
env_set VSCODE_PROXY_URI "${VSCODE_PROXY_URI:-}"
fi
switch_debug "$type" switch_debug "$type"
# #
if [ "$type" = "prod" ]; then if [ "$type" = "prod" ]; then
@ -429,6 +417,44 @@ electron_operate() {
env BUILD_FRONTEND=$BUILD_FRONTEND node ./electron/build.js $argv env BUILD_FRONTEND=$BUILD_FRONTEND node ./electron/build.js $argv
} }
# Expo App:同 electron 使用本机 Node,不依赖 Docker。
expo_operate() {
local usage="用法: ./cmd expo [build] | ./cmd expo dev [expo start 参数...] | ./cmd expo npm <参数...>"
check_node
case "${1:-build}" in
"build"|"dev")
if [[ "$1" != "dev" ]] && [[ $# -gt 1 ]]; then
error "$usage"
return 1
fi
if [ ! -d "./node_modules" ]; then
npm install --no-package-lock || return $?
fi
if [ ! -d "./electron/node_modules" ]; then
(cd electron && npm install --no-package-lock) || return $?
fi
if [ "$1" == "dev" ]; then
if [ ! -d "./resources/app/node_modules" ]; then
(cd resources/app && npm install --no-audit --no-fund) || return $?
fi
switch_debug dev
shift
node ./electron/build.js app-expo dev "$@"
else
node ./electron/build.js app-expo build
fi
;;
"npm")
shift
(cd "${WORK_DIR}/resources/app" && npm "$@")
;;
*)
error "$usage"
return 1
;;
esac
}
# 执行容器命令 # 执行容器命令
container_exec() { container_exec() {
local container=$1 local container=$1
@ -683,6 +709,9 @@ DooTask 管理脚本
serve, dev 启动开发模式 serve, dev 启动开发模式
build, prod 生产环境构建 build, prod 生产环境构建
electron 构建桌面应用 electron 构建桌面应用
expo [build] 编译并同步 Expo App 前端
expo dev [参数...] 启动 App 开发模式(Metro + 前端),参数透传给 expo start(如 -c)
expo npm <参数...> 在 Expo 子模块执行本机 npm
🔧 服务管理: 🔧 服务管理:
up [服务名] 启动容器 up [服务名] 启动容器
@ -734,6 +763,9 @@ Usage: ./cmd <command> [options]
serve, dev Start dev mode serve, dev Start dev mode
build, prod Production build build, prod Production build
electron Build desktop app electron Build desktop app
expo [build] Build and copy Expo App web assets
expo dev [args...] Start App dev mode (Metro + web), args passed to expo start (e.g. -c)
expo npm <args...> Run local npm in the Expo submodule
🔧 Services: 🔧 Services:
up [service] Start containers up [service] Start containers
@ -866,8 +898,11 @@ handle_install() {
exit 1 exit 1
fi fi
# 生成应用密钥 # 生成应用密钥(LaravelS 可能在 composer install 后已先行启动,需重启以加载新密钥)
[[ -z "$(env_get APP_KEY)" ]] && exec_judge "container_exec php 'php artisan key:generate'" "$(msg '生成密钥失败')" if [[ -z "$(env_get APP_KEY)" ]]; then
exec_judge "container_exec php 'php artisan key:generate'" "$(msg '生成密钥失败')"
restart_php
fi
# 设置生产模式 # 设置生产模式
switch_debug "false" switch_debug "false"
@ -1196,8 +1231,8 @@ if [[ "$1" == "help" ]] || [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]] || [[ $#
exit 0 exit 0
fi fi
# 非electron和permission命令需要检查Docker环境 # 非electron、expo和permission命令需要检查Docker环境
if [[ "$1" != "electron" ]] && [[ "$1" != "permission" ]]; then if [[ "$1" != "electron" ]] && [[ "$1" != "expo" ]] && [[ "$1" != "permission" ]]; then
check_docker check_docker
env_init env_init
fi fi
@ -1256,32 +1291,21 @@ case "$1" in
shift 1 shift 1
web_build prod web_build prod
;; ;;
"appbuild"|"buildapp") "expo")
shift 1 shift 1
electron_operate app "$@" expo_operate "$@"
exit $?
;; ;;
"electron") "electron")
shift 1 shift 1
electron_operate "$@" electron_operate "$@"
;; ;;
"eeui")
shift 1
cli="$@"
por=""
if [[ "$cli" == "build" ]]; then
cli="build --simple"
elif [[ "$cli" == "dev" ]]; then
por="-p 8880:8880"
fi
docker run $TTY_FLAG --rm -v ${WORK_DIR}/resources/mobile:/work -w /work ${por} kuaifan/eeui-cli:0.0.1 eeui ${cli}
;;
"npm") "npm")
shift 1 shift 1
npm "$@" npm "$@"
pushd electron || exit pushd electron || exit
npm "$@" npm "$@"
popd || exit popd || exit
docker run $TTY_FLAG --rm -v ${WORK_DIR}/resources/mobile:/work -w /work --entrypoint=/bin/bash node:16 -c "npm $@"
;; ;;
"doc") "doc")
shift 1 shift 1

View File

@ -23,7 +23,6 @@
"directorytree/ldaprecord-laravel": "^4.0", "directorytree/ldaprecord-laravel": "^4.0",
"firebase/php-jwt": "^7.1", "firebase/php-jwt": "^7.1",
"guzzlehttp/guzzle": "^7.3.0", "guzzlehttp/guzzle": "^7.3.0",
"hedeqiang/umeng": "^2.1",
"laravel/framework": "^13.0", "laravel/framework": "^13.0",
"laravel/tinker": "^3.0", "laravel/tinker": "^3.0",
"laravolt/avatar": "^6.5", "laravolt/avatar": "^6.5",

68
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "9563bf2796d1179ba8740789be1ef49b", "content-hash": "3ce4ce992d9f646b9ad6173c4f0d8605",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@ -1481,72 +1481,6 @@
], ],
"time": "2026-05-23T22:00:21+00:00" "time": "2026-05-23T22:00:21+00:00"
}, },
{
"name": "hedeqiang/umeng",
"version": "v2.1.6",
"source": {
"type": "git",
"url": "https://github.com/hedeqiang/UMeng-Push.git",
"reference": "f7556dd390e94d908fae715ed2978c7265f5f957"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hedeqiang/UMeng-Push/zipball/f7556dd390e94d908fae715ed2978c7265f5f957",
"reference": "f7556dd390e94d908fae715ed2978c7265f5f957",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/guzzle": "^6.3|^7.0",
"php": ">=7.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.19|^3.8"
},
"type": "library",
"extra": {
"hyperf": {
"config": "Hedeqiang\\UMeng\\ConfigProvider"
},
"laravel": {
"providers": [
"Hedeqiang\\UMeng\\PushServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Hedeqiang\\UMeng\\": "./src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "hedeqiang",
"email": "laravel_code@163.com"
},
{
"name": "housemecn",
"email": "houseme@outlook.com"
}
],
"description": "友盟推送",
"keywords": [
"U-Push",
"php",
"sdk",
"umeng",
"友盟"
],
"support": {
"issues": "https://github.com/hedeqiang/UMeng-Push/issues",
"source": "https://github.com/hedeqiang/UMeng-Push/tree/v2.1.6"
},
"time": "2022-09-28T08:52:46+00:00"
},
{ {
"name": "intervention/gif", "name": "intervention/gif",
"version": "5.0.1", "version": "5.0.1",

View File

@ -44,6 +44,13 @@ return [
// 临时文件自动清理天数(DeleteTmpTask) // 临时文件自动清理天数(DeleteTmpTask)
'auto_empty_temp_file' => env('AUTO_EMPTY_TEMP_FILE', 30), 'auto_empty_temp_file' => env('AUTO_EMPTY_TEMP_FILE', 30),
// APP 推送「系统配置」使用的官方 DooPush 应用(App Key 随客户端公开,不是密钥)
'doopush_system' => [
'base_url' => env('DOOPUSH_SYSTEM_BASE_URL', 'https://doopush.com/api/v1'),
'app_id' => env('DOOPUSH_SYSTEM_APP_ID', '10'),
'app_key' => env('DOOPUSH_SYSTEM_APP_KEY', 'dp_ak_eSqQPRXcjSR1xXtOrXGcHvjKGVEKarBA'),
],
// WebDAV 协议硬限制;用户可配置值不能超过这里的上限 // WebDAV 协议硬限制;用户可配置值不能超过这里的上限
'webdav' => [ 'webdav' => [
'max_file_bytes' => 1024 * 1024 * 1024, 'max_file_bytes' => 1024 * 1024 * 1024,

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAppPushDevicesTable extends Migration
{
public function up(): void
{
Schema::create('app_push_devices', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('userid')->index()->comment('用户ID');
$table->string('channel', 20)->comment('推送通道');
$table->string('device_id', 64)->comment('推送服务设备ID');
$table->string('vendor', 20)->default('')->comment('厂商通道');
$table->string('platform', 20)->default('')->comment('平台类型');
$table->string('device_hash', 64)->index()->comment('登录TOKEN MD5,关联UserDevice');
$table->string('device', 255)->default('')->comment('设备型号');
$table->string('version', 100)->default('')->comment('应用版本号');
$table->text('ua')->nullable()->comment('userAgent');
$table->tinyInteger('is_notified')->default(0)->comment('通知权限');
$table->timestamps();
$table->unique(['channel', 'device_id']);
});
}
public function down(): void
{
Schema::dropIfExists('app_push_devices');
}
}

View File

@ -0,0 +1,35 @@
<?php
use App\Models\Setting;
use App\Module\Base;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 移除友盟推送:删除别名、日志表及推送设置中的友盟参数,DooPush 开关迁移为三档模式
*/
public function up(): void
{
Schema::dropIfExists('umeng_alias');
Schema::dropIfExists('umeng_logs');
$row = Setting::whereName('appPushSetting')->first();
if ($row) {
$setting = Base::string2array($row->setting);
// DooPush 改为三档,升级后统一使用系统配置;原自定义凭证保留,需要时可切回「自定义」。
$setting['doopush'] = 'system';
foreach (['push', 'ios_key', 'ios_secret', 'android_key', 'android_secret'] as $key) {
unset($setting[$key]);
}
$row->updateInstance(['setting' => $setting]);
$row->save();
}
}
public function down(): void
{
// 删除表无法恢复
}
};

View File

@ -1,7 +1,7 @@
services: services:
php: php:
container_name: "dootask-php-${APP_ID}" container_name: "dootask-php-${APP_ID}"
image: "kuaifan/php:swoole-8.4" image: "kuaifan/php:swoole-8.4.27"
shm_size: 2G shm_size: 2G
ulimits: ulimits:
core: core:
@ -45,6 +45,8 @@ services:
- "${APP_SSL_PORT:-0}:443" - "${APP_SSL_PORT:-0}:443"
environment: environment:
APP_SCHEME: "${APP_SCHEME:-auto}" APP_SCHEME: "${APP_SCHEME:-auto}"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes: volumes:
- ./docker/nginx/default.conf:/etc/nginx/templates/default.conf.template - ./docker/nginx/default.conf:/etc/nginx/templates/default.conf.template
- ./:/var/www - ./:/var/www

View File

@ -1518,7 +1518,8 @@ session.sid_bits_per_character = 5
; Development Value: On ; Development Value: On
; Production Value: On ; Production Value: On
; http://php.net/session.upload-progress.enabled ; http://php.net/session.upload-progress.enabled
;session.upload_progress.enabled = On ; 关闭以缓解 CVE-2026-109619(项目使用 Laravel session,不依赖此功能)
session.upload_progress.enabled = Off
; Cleanup the progress information as soon as all POST data has been read ; Cleanup the progress information as soon as all POST data has been read
; (i.e. upload completed). ; (i.e. upload completed).

View File

@ -7,14 +7,14 @@
> - 事件总线: `resources/assets/js/store/events.js`(mitt 实例) > - 事件总线: `resources/assets/js/store/events.js`(mitt 实例)
> - 仅匹配裸 `emitter.emit/on/off(` 调用;`xxx.emitter.emit(`(如 Quill 内部 emitter)不属于本总线,已排除 > - 仅匹配裸 `emitter.emit/on/off(` 调用;`xxx.emitter.emit(`(如 Quill 内部 emitter)不属于本总线,已排除
共 **30** 个静态可解析事件,**125** 处 `emitter.emit/on/off` 调用。 共 **31** 个静态可解析事件,**128** 处 `emitter.emit/on/off` 调用。
## 事件清单 ## 事件清单
### `addMeeting` ### `addMeeting`
- **emit(10)** - **emit(10)**
- `resources/assets/js/App.vue:422` - `resources/assets/js/App.vue:469`
- `resources/assets/js/pages/manage.vue:1278` - `resources/assets/js/pages/manage.vue:1278`
- `resources/assets/js/pages/manage.vue:1285` - `resources/assets/js/pages/manage.vue:1285`
- `resources/assets/js/pages/manage/application.vue:1198` - `resources/assets/js/pages/manage/application.vue:1198`
@ -23,7 +23,7 @@
- `resources/assets/js/pages/manage/components/DialogView/index.vue:621` - `resources/assets/js/pages/manage/components/DialogView/index.vue:621`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2040` - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2040`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2048` - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2048`
- `resources/assets/js/pages/manage/messenger.vue:1227` - `resources/assets/js/pages/manage/messenger.vue:1408`
- **on(1)** - **on(1)**
- `resources/assets/js/pages/manage/components/MeetingManager/index.vue:187` - `resources/assets/js/pages/manage/components/MeetingManager/index.vue:187`
- **off(1)** - **off(1)**
@ -66,21 +66,30 @@
- **off(1)** - **off(1)**
- `resources/assets/js/components/AIAssistant/float-button.vue:165` - `resources/assets/js/components/AIAssistant/float-button.vue:165`
### `checkAppUpdate`
- **emit(1)**
- `resources/assets/js/pages/manage/setting/index.vue:249`
- **on(1)**
- `resources/assets/js/App.vue:148`
- **off(1)**
- `resources/assets/js/App.vue:153`
### `clickAgainDialog` ### `clickAgainDialog`
- **emit(1)** - **emit(1)**
- `resources/assets/js/components/Mobile/Tabbar.vue:188` - `resources/assets/js/components/Mobile/Tabbar.vue:188`
- **on(1)** - **on(1)**
- `resources/assets/js/pages/manage/messenger.vue:344` - `resources/assets/js/pages/manage/messenger.vue:370`
- **off(1)** - **off(1)**
- `resources/assets/js/pages/manage/messenger.vue:348` - `resources/assets/js/pages/manage/messenger.vue:374`
### `createGroup` ### `createGroup`
- **emit(3)** - **emit(3)**
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2894` - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2894`
- `resources/assets/js/pages/manage/components/UserDetail.vue:294` - `resources/assets/js/pages/manage/components/UserDetail.vue:294`
- `resources/assets/js/pages/manage/messenger.vue:1232` - `resources/assets/js/pages/manage/messenger.vue:1413`
- **on(1)** - **on(1)**
- `resources/assets/js/pages/manage.vue:601` - `resources/assets/js/pages/manage.vue:601`
- **off(1)** - **off(1)**
@ -121,7 +130,7 @@
### `observeMicroApp:open` ### `observeMicroApp:open`
- **emit(1)** - **emit(1)**
- `resources/assets/js/store/actions.js:5479` - `resources/assets/js/store/actions.js:5482`
- **on(1)** - **on(1)**
- `resources/assets/js/components/MicroApps/index.vue:202` - `resources/assets/js/components/MicroApps/index.vue:202`
- **off(1)** - **off(1)**
@ -199,7 +208,7 @@
### `openProjectInvite` ### `openProjectInvite`
- **emit(1)** - **emit(1)**
- `resources/assets/js/App.vue:434` - `resources/assets/js/App.vue:481`
- **on(1)** - **on(1)**
- `resources/assets/js/pages/manage/components/ProjectInvite.vue:83` - `resources/assets/js/pages/manage/components/ProjectInvite.vue:83`
- **off(1)** - **off(1)**
@ -238,7 +247,7 @@
- `resources/assets/js/components/UserAvatar/index.vue:184` - `resources/assets/js/components/UserAvatar/index.vue:184`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2963` - `resources/assets/js/pages/manage/components/DialogWrapper.vue:2963`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:4546` - `resources/assets/js/pages/manage/components/DialogWrapper.vue:4546`
- `resources/assets/js/pages/manage/messenger.vue:1237` - `resources/assets/js/pages/manage/messenger.vue:1418`
- **on(1)** - **on(1)**
- `resources/assets/js/pages/manage/components/UserDetail.vue:166` - `resources/assets/js/pages/manage/components/UserDetail.vue:166`
- **off(1)** - **off(1)**
@ -276,7 +285,7 @@
- **emit(2)** - **emit(2)**
- `resources/assets/js/pages/manage.vue:1153` - `resources/assets/js/pages/manage.vue:1153`
- `resources/assets/js/pages/manage/setting/index.vue:198` - `resources/assets/js/pages/manage/setting/index.vue:200`
- **on(1)** - **on(1)**
- `resources/assets/js/components/RightBottom.vue:65` - `resources/assets/js/components/RightBottom.vue:65`
- **off(1)** - **off(1)**
@ -323,7 +332,7 @@
## 统计 ## 统计
- 事件总数(静态可解析): **30** - 事件总数(静态可解析): **31**
- 只 emit 无 on(疑似死事件): **0** - 只 emit 无 on(疑似死事件): **0**
- 只 on 无 emit(无人发射): **1** — `aiAssistantFloatButtonVisibilityChanged` - 只 on 无 emit(无人发射): **1** — `aiAssistantFloatButtonVisibilityChanged`
- 动态事件名调用: **1** - 动态事件名调用: **1**

143
electron/build.js vendored
View File

@ -26,7 +26,6 @@ const architectures = ["arm64", "x64"];
let buildChecked = false, let buildChecked = false,
updaterChecked = false; updaterChecked = false;
const shellQuote = (value) => `'${String(value).replace(/'/g, `'\\''`)}'`;
const elapsedSeconds = (startTime) => `${((Date.now() - startTime) / 1000).toFixed(1)}s`; const elapsedSeconds = (startTime) => `${((Date.now() - startTime) / 1000).toFixed(1)}s`;
/** /**
@ -336,8 +335,12 @@ async function uploadDraftFile(client, localFile, version) {
* @param data * @param data
*/ */
async function startBuild(data) { async function startBuild(data) {
if (BUILD_FRONTEND === 'build' && !buildChecked) { if ((BUILD_FRONTEND === 'build' || data.id === 'app-expo') && !buildChecked) {
buildChecked = true buildChecked = true
// Expo 每次重新编译,避免把上次桌面端或旧版本的文件带进内测包。
if (data.id === 'app-expo') {
fse.emptyDirSync(electronDir);
}
fs.mkdirSync(electronDir, { recursive: true }); fs.mkdirSync(electronDir, { recursive: true });
fse.copySync(path.resolve(__dirname, "index.html"), path.resolve(electronDir, "index.html")) fse.copySync(path.resolve(__dirname, "index.html"), path.resolve(electronDir, "index.html"))
child_process.execSync("npx vite build -- fromcmd electronBuild", {stdio: "inherit"}); child_process.execSync("npx vite build -- fromcmd electronBuild", {stdio: "inherit"});
@ -355,7 +358,7 @@ async function startBuild(data) {
apiUrl: utils.formatUrl(data.url) + "api/", apiUrl: utils.formatUrl(data.url) + "api/",
} }
// information // information
if (data.id === 'app') { if (data.id === 'app-expo') {
console.log("\n=== 编译信息 ==="); console.log("\n=== 编译信息 ===");
console.log("名称:", data.name); console.log("名称:", data.name);
console.log("版本:", config.version + ` (${config.codeVerson})`); console.log("版本:", config.version + ` (${config.codeVerson})`);
@ -385,11 +388,16 @@ async function startBuild(data) {
// config.js // config.js
fs.writeFileSync(electronDir + "/config.js", "window.systemInfo = " + JSON.stringify(systemInfo), 'utf8'); fs.writeFileSync(electronDir + "/config.js", "window.systemInfo = " + JSON.stringify(systemInfo), 'utf8');
fs.writeFileSync(electronDir + "/dark", '', 'utf8'); fs.writeFileSync(electronDir + "/dark", '', 'utf8');
if (data.id !== 'app-expo') {
fs.writeFileSync(nativeCachePath, utils.formatUrl(data.url)); fs.writeFileSync(nativeCachePath, utils.formatUrl(data.url));
fs.writeFileSync(devloadCachePath, "", 'utf8'); fs.writeFileSync(devloadCachePath, "", 'utf8');
}
// index.html // index.html
let manifestFile = path.resolve(electronDir, "manifest.json"); let manifestFile = path.resolve(electronDir, "manifest.json");
if (!fs.existsSync(manifestFile)) { if (!fs.existsSync(manifestFile)) {
if (data.id === 'app-expo') {
throw new Error("manifest.json 未找到,Expo 前端构建失败");
}
console.error("manifest.json 未找到"); console.error("manifest.json 未找到");
return; return;
} }
@ -401,49 +409,16 @@ async function startBuild(data) {
indexString = indexString.replace("<!--script-->", `<script type="module" src="./${manifestContent['resources/assets/js/app.js']['file']}"></script>`); indexString = indexString.replace("<!--script-->", `<script type="module" src="./${manifestContent['resources/assets/js/app.js']['file']}"></script>`);
fs.writeFileSync(indexFile, indexString, 'utf8'); fs.writeFileSync(indexFile, indexString, 'utf8');
// //
if (data.id === 'app') { if (data.id === 'app-expo') {
const eeuiDir = path.resolve(__dirname, "../resources/mobile"); const appDir = path.resolve(__dirname, "../resources/app");
const publicDir = path.resolve(__dirname, "../resources/mobile/src/public"); const webDir = path.join(appDir, "assets/web");
const containerName = `dootask-eeui-${Date.now()}-${process.pid}`; fse.emptyDirSync(webDir);
fse.removeSync(publicDir) fse.copySync(electronDir, webDir);
fse.copySync(electronDir, publicDir) fs.writeFileSync(path.join(appDir, "version.json"), JSON.stringify({
if (argv[3] === "publish") { version: config.version,
// Android config codeVerson: config.codeVerson,
const gradleFile = path.resolve(eeuiDir, "platforms/android/eeuiApp/local.properties") }, null, 2) + "\n", 'utf8');
let gradleResult = fs.existsSync(gradleFile) ? fs.readFileSync(gradleFile, 'utf8') : ""; console.log("Expo 前端资源已同步至 resources/app/assets/web/");
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)
})
}
return; return;
} }
const output = `dist/${data.id.replace(/\./g, '-')}/${platform}` const output = `dist/${data.id.replace(/\./g, '-')}/${platform}`
@ -533,17 +508,67 @@ if (["dev"].includes(argv[2])) {
fs.writeFileSync(devloadCachePath, utils.formatUrl("localhost:" + env.parsed.APP_PORT), 'utf8'); fs.writeFileSync(devloadCachePath, utils.formatUrl("localhost:" + env.parsed.APP_PORT), 'utf8');
child_process.spawn("npx", ["vite", "--", "fromcmd", "electronDev"], {stdio: "inherit"}); child_process.spawn("npx", ["vite", "--", "fromcmd", "electronDev"], {stdio: "inherit"});
child_process.spawn("npm", ["run", "start-quiet"], {stdio: "inherit", cwd: "electron"}); child_process.spawn("npm", ["run", "start-quiet"], {stdio: "inherit", cwd: "electron"});
} else if (["app"].includes(argv[2])) { } else if (argv[2] === 'app-expo') {
// 编译前端页面给 App if ((argv[3] && !['build', 'dev'].includes(argv[3])) || (argv[3] !== 'dev' && argv.length > 4)) {
let mobileSrcDir = path.resolve(__dirname, "../resources/mobile"); console.error("用法: node ./electron/build.js app-expo [build|dev [expo start 参数...]]");
if (!fs.existsSync(mobileSrcDir)) { process.exit(1);
console.error("resources/mobile 未找到");
process.exit()
} }
const appDir = path.resolve(__dirname, "../resources/app");
if (!fs.existsSync(path.resolve(appDir, "app.config.ts"))) {
console.error("resources/app 未找到,请先初始化 Expo 子模块");
process.exit(1);
}
if (argv[3] === 'dev') {
// 开发模式:前端 vite + Metro;键盘交给 Metro(r 重载等),任一退出则全部结束
// 地址优先取命令行环境变量,其次按 VSCODE_PROXY_URI 端口模板推导,都没有则 Metro 由 Expo 自动取局域网地址、前端取本机局域网 IP
// 手动指定 EXPO_PACKAGER_PROXY_URL 时端口固定(METRO_PORT,默认 2220),否则从 2220 起自动找空闲端口
// dev 之后的参数原样透传给 expo start(如 -c 清缓存)
const appPort = env.parsed.APP_PORT;
const proxyUri = process.env.VSCODE_PROXY_URI || '';
const proxyUrl = port => proxyUri.includes('{{port}}') ? proxyUri.replace(/\{\{port\}\}/g, port).replace(/\/+$/, '') : '';
const lanIp = Object.values(require('os').networkInterfaces()).flat().find(item => item.family === 'IPv4' && !item.internal)?.address;
const webUrl = process.env.EXPO_PUBLIC_WEB_URL || proxyUrl(appPort) || (lanIp ? `http://${lanIp}:${appPort}` : '');
const fixedPort = !!process.env.EXPO_PACKAGER_PROXY_URL;
const isPortFree = port => new Promise(resolve => {
const server = require('net').createServer();
server.once('error', () => resolve(false));
server.listen(port, () => server.close(() => resolve(true)));
});
(async () => {
let metroPort = Number(process.env.METRO_PORT) || 2220;
// 跳过前端端口 APP_PORT:nginx 未启动时它也是空闲的,被 Metro 占用会导致 nginx 起不来
while (metroPort == appPort || !await isPortFree(metroPort)) {
if (fixedPort || metroPort >= 2299) {
console.error(`Metro 端口 ${metroPort} 已被占用,请先关闭旧的 Metro`);
process.exit(1);
}
metroPort++;
}
const packagerUrl = process.env.EXPO_PACKAGER_PROXY_URL || proxyUrl(metroPort);
console.log(`Metro 地址:${packagerUrl || '自动(局域网)'}`);
console.log(`前端地址:${webUrl || '本地 Web 服务'}`);
const appEnv = {...process.env, EXPO_PUBLIC_WEB_URL: webUrl};
if (packagerUrl) appEnv.EXPO_PACKAGER_PROXY_URL = packagerUrl;
// vite 直接用当前 node 运行入口文件:经 npx → sh → node 多层启动时,只杀 npx 会留下孤儿 vite 占着 517x 端口
const vite = child_process.spawn(process.execPath, [require.resolve('vite/bin/vite.js'), "--", "fromcmd"], {stdio: ["ignore", "inherit", "inherit"]});
const metro = child_process.spawn("npx", ["expo", "start", "--dev-client", "--port", String(metroPort), ...argv.slice(4)], {stdio: "inherit", cwd: appDir, env: appEnv});
const children = [vite, metro];
let exiting = false;
const stopAll = (code) => {
if (exiting) return;
exiting = true;
process.exitCode = code;
children.forEach(child => child.exitCode === null && child.kill('SIGTERM'));
};
children.forEach(child => child.on('exit', code => stopAll(code || 0)));
['SIGINT', 'SIGTERM', 'SIGHUP'].forEach(signal => process.on(signal, () => stopAll(0)));
})();
} else {
startBuild({ startBuild({
name: 'App', name: 'App',
id: 'app', id: 'app-expo',
platform: '', // public 是前端“尚未设置服务器”的占位域名,不是静态资源的 origin。
// electronBuild 的 ./ 资源路径在 localhost:22223/ 下加载,路由自动使用 hash。
url: 'http://public/', url: 'http://public/',
configure: { configure: {
platform: '', platform: '',
@ -552,16 +577,20 @@ if (["dev"].includes(argv[2])) {
release: true, release: true,
notarize: false, notarize: false,
} }
}) }).catch(error => {
console.error(error.message || error);
process.exitCode = 1;
});
}
} else if (["android-upload"].includes(argv[2])) { } else if (["android-upload"].includes(argv[2])) {
// 上传安卓文件到 R2 draft(GitHub Actions) // 上传安卓文件到 R2 draft(GitHub Actions):参数为 APK 所在目录(app-build.yml 产出的 app-android)
(async () => { (async () => {
if (!r2.r2Configured()) { if (!r2.r2Configured()) {
console.error("缺少 R2_* 环境变量(R2_ACCESS_KEY_ID/R2_SECRET_ACCESS_KEY/R2_ENDPOINT/R2_BUCKET)") console.error("缺少 R2_* 环境变量(R2_ACCESS_KEY_ID/R2_SECRET_ACCESS_KEY/R2_ENDPOINT/R2_BUCKET)")
process.exit(1) process.exit(1)
} }
const client = r2.createR2Client() const client = r2.createR2Client()
const releaseDir = path.resolve(__dirname, "../resources/mobile/platforms/android/eeuiApp/app/build/outputs/apk/release"); const releaseDir = path.resolve(argv[3] || path.resolve(__dirname, "../resources/app/android/app/build/outputs/apk/release"));
if (!fs.existsSync(releaseDir)) { if (!fs.existsSync(releaseDir)) {
console.error("发布文件未找到") console.error("发布文件未找到")
process.exit(1) process.exit(1)

View File

@ -13,7 +13,7 @@ const replacements = [
]; ];
function patchSource(source, version) { function patchSource(source, version) {
if (!['26.8.1', '26.15.3'].includes(version)) { if (!['26.8.1', '26.15.3', '26.18.0'].includes(version)) {
throw new Error(`Unsupported app-builder-lib ${version}; review the macOS keychain workaround before building.`); throw new Error(`Unsupported app-builder-lib ${version}; review the macOS keychain workaround before building.`);
} }
const count = (text) => source.split(text).length - 1; const count = (text) => source.split(text).length - 1;

View File

@ -1092,3 +1092,11 @@ WebDAV 未启用或你没有使用权限
已开始更新系统 已开始更新系统
关联任务 关联任务
解除关联任务 解除关联任务
推送设备ID必须为1至64位数字
推送厂商错误
推送Token不能为空
推送服务注册失败,请稍后再试
推送设备未注册,请稍后再试
推送服务已被停用
推送设备绑定失败,请稍后再试
推送模式错误

View File

@ -410,7 +410,6 @@ SMTP服务器
参数为空 参数为空
参数配置 参数配置
参数错误 参数错误
友盟推送
发布成功 发布成功
发送图片 发送图片
发送成功 发送成功
@ -2844,3 +2843,17 @@ APP 新版本
更新内容 更新内容
按字母显示 按字母显示
按部门显示 按部门显示
DooPush 推送
App ID
App Secret
服务地址
DooTask 新版本 (*) 已发布,建议更新以获得更好的使用体验。
已是最新版本
检查更新失败,请稍后再试
当前服务器版本较低,请等待服务器升级后再更新
推送服务
系统配置
使用官方推送服务,无需配置。
使用自己的 DooPush 应用,需搭配自行打包的 App。
选择复制
原文

View File

@ -5903,18 +5903,6 @@
"id": "Kesalahan parameter", "id": "Kesalahan parameter",
"ru": "Ошибка параметра" "ru": "Ошибка параметра"
}, },
{
"key": "友盟推送",
"zh": "",
"zh-CHT": "友盟推送",
"en": "UMeng push",
"ko": "UMeng 푸시",
"ja": "UMengプッシュ",
"de": "UMeng-Push",
"fr": "Push UMeng",
"id": "Push UMeng",
"ru": "UMeng push"
},
{ {
"key": "发布成功", "key": "发布成功",
"zh": "", "zh": "",
@ -40966,5 +40954,269 @@
"fr": "Dissocier la tâche", "fr": "Dissocier la tâche",
"id": "Putuskan tautan tugas", "id": "Putuskan tautan tugas",
"ru": "Отвязать задачу" "ru": "Отвязать задачу"
},
{
"key": "DooPush 推送",
"zh": "",
"zh-CHT": "DooPush 推送",
"en": "DooPush Notifications",
"ko": "DooPush 푸시 알림",
"ja": "DooPush プッシュ通知",
"de": "DooPush-Benachrichtigungen",
"fr": "Notifications DooPush",
"id": "Notifikasi DooPush",
"ru": "Уведомления DooPush"
},
{
"key": "App ID",
"zh": "",
"zh-CHT": "App ID",
"en": "App ID",
"ko": "앱 ID",
"ja": "App ID",
"de": "App ID",
"fr": "App ID",
"id": "App ID",
"ru": "App ID"
},
{
"key": "App Secret",
"zh": "",
"zh-CHT": "App Secret",
"en": "App Secret",
"ko": "앱 시크릿",
"ja": "App Secret",
"de": "App Secret",
"fr": "App Secret",
"id": "App Secret",
"ru": "App Secret"
},
{
"key": "服务地址",
"zh": "",
"zh-CHT": "服務地址",
"en": "Service URL",
"ko": "서비스 주소",
"ja": "サービスURL",
"de": "Dienst-URL",
"fr": "URL du service",
"id": "URL layanan",
"ru": "URL сервиса"
},
{
"key": "DooTask 新版本 (%T1) 已发布,建议更新以获得更好的使用体验。",
"zh": "",
"zh-CHT": "DooTask 新版本 (%T1) 已發布,建議更新以獲得更好的使用體驗。",
"en": "DooTask (%T1) is now available. Update for a better experience.",
"ko": "DooTask 새 버전 (%T1)이(가) 출시되었습니다. 더 나은 사용 경험을 위해 업데이트하세요.",
"ja": "DooTask の新しいバージョン (%T1) がリリースされました。より快適にご利用いただくため、更新をおすすめします。",
"de": "DooTask (%T1) ist verfügbar. Aktualisiere für ein besseres Nutzungserlebnis.",
"fr": "DooTask (%T1) est disponible. Mettez à jour pour une meilleure expérience.",
"id": "DooTask versi baru (%T1) telah dirilis. Perbarui untuk pengalaman yang lebih baik.",
"ru": "Доступна новая версия DooTask (%T1). Обновите приложение для более удобной работы."
},
{
"key": "已是最新版本",
"zh": "",
"zh-CHT": "已是最新版本",
"en": "You're on the latest version",
"ko": "최신 버전입니다",
"ja": "最新バージョンです",
"de": "Du verwendest die neueste Version",
"fr": "Vous utilisez la dernière version",
"id": "Anda sudah menggunakan versi terbaru",
"ru": "У вас последняя версия"
},
{
"key": "检查更新失败,请稍后再试",
"zh": "",
"zh-CHT": "檢查更新失敗,請稍後再試",
"en": "Failed to check for updates. Please try again later.",
"ko": "업데이트 확인에 실패했습니다. 잠시 후 다시 시도하세요.",
"ja": "更新の確認に失敗しました。しばらくしてからもう一度お試しください。",
"de": "Suche nach Updates fehlgeschlagen. Bitte versuche es später erneut.",
"fr": "Impossible de rechercher des mises à jour. Veuillez réessayer plus tard.",
"id": "Gagal memeriksa pembaruan. Silakan coba lagi nanti.",
"ru": "Не удалось проверить обновления. Повторите попытку позже."
},
{
"key": "推送设备ID必须为1至64位数字",
"zh": "",
"zh-CHT": "推送裝置ID必須為1至64位數字",
"en": "Push device ID must be 1 to 64 digits",
"ko": "푸시 기기 ID는 1~64자리 숫자여야 합니다",
"ja": "プッシュデバイスIDは1〜64桁の数字である必要があります",
"de": "Die Push-Geräte-ID muss aus 1 bis 64 Ziffern bestehen",
"fr": "L'ID de l'appareil push doit comporter de 1 à 64 chiffres",
"id": "ID perangkat push harus berupa 1 hingga 64 digit angka",
"ru": "ID устройства для push-уведомлений должен содержать от 1 до 64 цифр"
},
{
"key": "推送厂商错误",
"zh": "",
"zh-CHT": "推送廠商錯誤",
"en": "Invalid push vendor",
"ko": "푸시 제조사가 올바르지 않습니다",
"ja": "プッシュベンダーが正しくありません",
"de": "Ungültiger Push-Anbieter",
"fr": "Fournisseur push invalide",
"id": "Vendor push tidak valid",
"ru": "Неверный поставщик push-уведомлений"
},
{
"key": "当前服务器版本较低,请等待服务器升级后再更新",
"zh": "",
"zh-CHT": "目前伺服器版本較低,請等待伺服器升級後再更新",
"en": "The server version is outdated. Please update after the server has been upgraded.",
"ko": "현재 서버 버전이 낮습니다. 서버가 업그레이드된 후 업데이트하세요.",
"ja": "現在のサーバーのバージョンが古いため、サーバーのアップグレード後に更新してください。",
"de": "Die Serverversion ist veraltet. Bitte aktualisiere erst, nachdem der Server aktualisiert wurde.",
"fr": "La version du serveur est obsolète. Veuillez mettre à jour après la mise à niveau du serveur.",
"id": "Versi server saat ini lebih lama. Silakan perbarui setelah server ditingkatkan.",
"ru": "Версия сервера устарела. Обновите приложение после обновления сервера."
},
{
"key": "推送服务",
"zh": "",
"zh-CHT": "推送服務",
"en": "Push service",
"ko": "푸시 서비스",
"ja": "プッシュサービス",
"de": "Push-Dienst",
"fr": "Service de notifications",
"id": "Layanan push",
"ru": "Служба push-уведомлений"
},
{
"key": "系统配置",
"zh": "",
"zh-CHT": "系統配置",
"en": "System default",
"ko": "시스템 기본값",
"ja": "システム既定",
"de": "Systemstandard",
"fr": "Configuration système",
"id": "Bawaan sistem",
"ru": "Системная настройка"
},
{
"key": "使用官方推送服务,无需配置。",
"zh": "",
"zh-CHT": "使用官方推送服務,無需配置。",
"en": "Uses the official push service. No setup required.",
"ko": "공식 푸시 서비스를 사용하며 별도 설정이 필요 없습니다.",
"ja": "公式のプッシュサービスを使用します。設定は不要です。",
"de": "Verwendet den offiziellen Push-Dienst. Keine Einrichtung erforderlich.",
"fr": "Utilise le service de notifications officiel. Aucune configuration requise.",
"id": "Menggunakan layanan push resmi. Tidak perlu konfigurasi.",
"ru": "Используется официальная служба push-уведомлений. Настройка не требуется."
},
{
"key": "使用自己的 DooPush 应用,需搭配自行打包的 App。",
"zh": "",
"zh-CHT": "使用自己的 DooPush 應用,需搭配自行打包的 App。",
"en": "Uses your own DooPush app. Requires a self-built App.",
"ko": "자체 DooPush 앱을 사용하며, 직접 빌드한 App과 함께 사용해야 합니다.",
"ja": "独自の DooPush アプリを使用します。自前でビルドした App が必要です。",
"de": "Verwendet Ihre eigene DooPush-App. Erfordert eine selbst erstellte App.",
"fr": "Utilise votre propre application DooPush. Nécessite une App compilée par vos soins.",
"id": "Menggunakan aplikasi DooPush Anda sendiri. Memerlukan App yang dibangun sendiri.",
"ru": "Используется собственное приложение DooPush. Требуется самостоятельно собранное App."
},
{
"key": "推送Token不能为空",
"zh": "",
"zh-CHT": "推送Token不能為空",
"en": "Push token cannot be empty",
"ko": "푸시 토큰은 비워 둘 수 없습니다",
"ja": "プッシュトークンは必須です",
"de": "Push-Token darf nicht leer sein",
"fr": "Le jeton de notification ne peut pas être vide",
"id": "Token push tidak boleh kosong",
"ru": "Push-токен не может быть пустым"
},
{
"key": "推送服务注册失败,请稍后再试",
"zh": "",
"zh-CHT": "推送服務註冊失敗,請稍後再試",
"en": "Failed to register with the push service. Please try again later.",
"ko": "푸시 서비스 등록에 실패했습니다. 잠시 후 다시 시도하세요.",
"ja": "プッシュサービスへの登録に失敗しました。しばらくしてから再試行してください。",
"de": "Registrierung beim Push-Dienst fehlgeschlagen. Bitte später erneut versuchen.",
"fr": "Échec de l'inscription au service de notifications. Veuillez réessayer plus tard.",
"id": "Gagal mendaftar ke layanan push. Silakan coba lagi nanti.",
"ru": "Не удалось зарегистрироваться в службе push-уведомлений. Повторите попытку позже."
},
{
"key": "推送设备未注册,请稍后再试",
"zh": "",
"zh-CHT": "推送設備未註冊,請稍後再試",
"en": "Push device is not registered yet. Please try again later.",
"ko": "푸시 기기가 아직 등록되지 않았습니다. 잠시 후 다시 시도하세요.",
"ja": "プッシュデバイスが未登録です。しばらくしてから再試行してください。",
"de": "Push-Gerät ist noch nicht registriert. Bitte später erneut versuchen.",
"fr": "L'appareil n'est pas encore enregistré. Veuillez réessayer plus tard.",
"id": "Perangkat push belum terdaftar. Silakan coba lagi nanti.",
"ru": "Устройство ещё не зарегистрировано. Повторите попытку позже."
},
{
"key": "推送服务已被停用",
"zh": "",
"zh-CHT": "推送服務已被停用",
"en": "Push service has been disabled",
"ko": "푸시 서비스가 비활성화되었습니다",
"ja": "プッシュサービスは停止されています",
"de": "Push-Dienst wurde deaktiviert",
"fr": "Le service de notifications a été désactivé",
"id": "Layanan push telah dinonaktifkan",
"ru": "Служба push-уведомлений отключена"
},
{
"key": "推送设备绑定失败,请稍后再试",
"zh": "",
"zh-CHT": "推送設備綁定失敗,請稍後再試",
"en": "Failed to bind push device. Please try again later.",
"ko": "푸시 기기 연결에 실패했습니다. 잠시 후 다시 시도하세요.",
"ja": "プッシュデバイスの紐付けに失敗しました。しばらくしてから再試行してください。",
"de": "Push-Gerät konnte nicht verknüpft werden. Bitte später erneut versuchen.",
"fr": "Échec de l'association de l'appareil. Veuillez réessayer plus tard.",
"id": "Gagal menautkan perangkat push. Silakan coba lagi nanti.",
"ru": "Не удалось привязать устройство. Повторите попытку позже."
},
{
"key": "推送模式错误",
"zh": "",
"zh-CHT": "推送模式錯誤",
"en": "Invalid push mode",
"ko": "잘못된 푸시 모드입니다",
"ja": "プッシュモードが正しくありません",
"de": "Ungültiger Push-Modus",
"fr": "Mode de notification invalide",
"id": "Mode push tidak valid",
"ru": "Неверный режим push-уведомлений"
},
{
"key": "选择复制",
"zh": "",
"zh-CHT": "選擇複製",
"en": "Select & Copy",
"ko": "선택하여 복사",
"ja": "選択してコピー",
"de": "Auswählen und kopieren",
"fr": "Sélectionner et copier",
"id": "Pilih dan Salin",
"ru": "Выбрать и копировать"
},
{
"key": "原文",
"zh": "",
"zh-CHT": "原文",
"en": "Source",
"ko": "원문",
"ja": "原文",
"de": "Quelltext",
"fr": "Source",
"id": "Sumber",
"ru": "Исходный текст"
} }
] ]

View File

@ -1,7 +1,7 @@
{ {
"name": "DooTask", "name": "DooTask",
"version": "1.9.66", "version": "1.10.38",
"codeVerson": 245, "codeVerson": 247,
"description": "DooTask is task management system.", "description": "DooTask is task management system.",
"scripts": { "scripts": {
"start": "./cmd dev", "start": "./cmd dev",
@ -43,6 +43,7 @@
"html-to-md": "^0.8.8", "html-to-md": "^0.8.8",
"inquirer": "^8.2.0", "inquirer": "^8.2.0",
"internal-ip": "^6.2.0", "internal-ip": "^6.2.0",
"ionicons": "^8.1.0",
"jquery": "^3.6.4", "jquery": "^3.6.4",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"less": "^4.1.2", "less": "^4.1.2",

View File

@ -1 +1 @@
import{n as m}from"./app.d80882e4.js";import"./jquery.ead8aad7.js";import"./@babel.9410f858.js";import"./dayjs.2db954dd.js";import"./localforage.44d37169.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var p=function(){var t=this,r=t.$createElement;return t._self._c,t._m(0)},e=[function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"page-404"},[i("div",{staticClass:"flex-center position-ref full-height"},[i("div",{staticClass:"code"},[t._v("404")]),i("div",{staticClass:"message"},[t._v("Not Found")])])])}];const s={},o={};var _=m(s,p,e,!1,n,"7d7154a8",null,null);function n(t){for(let r in o)this[r]=o[r]}var it=function(){return _.exports}();export{it as default}; import{n as m}from"./app.78a291d1.js";import"./jquery.9174f09e.js";import"./@babel.9410f858.js";import"./dayjs.ce1ced10.js";import"./localforage.8ee247f7.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var p=function(){var t=this,r=t.$createElement;return t._self._c,t._m(0)},e=[function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"page-404"},[i("div",{staticClass:"flex-center position-ref full-height"},[i("div",{staticClass:"code"},[t._v("404")]),i("div",{staticClass:"message"},[t._v("Not Found")])])])}];const s={},o={};var _=m(s,p,e,!1,n,"7d7154a8",null,null);function n(t){for(let r in o)this[r]=o[r]}var it=function(){return _.exports}();export{it as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{m as i}from"./vuex.cc7cb26e.js";import{n as d}from"./app.d80882e4.js";var o=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("Modal",{attrs:{value:t.value,title:t.modalTitle,"mask-closable":!1,width:"520"},on:{input:function(s){return t.$emit("input",s)}}},[e("div",{staticClass:"department-owner-view-modal"},[e("Alert",{attrs:{type:"info","show-icon":""}},[t.scopeOnly?[t._v(t._s(t.$L("\u9009\u62E9\u9700\u8981\u7EDF\u8BA1\u7684\u90E8\u95E8\uFF0C\u5305\u542B\u6240\u9009\u90E8\u95E8\u53CA\u5176\u6240\u6709\u4E0B\u7EA7\u90E8\u95E8\u3002")))]:[t._v(t._s(t.$L("\u90E8\u95E8\u8303\u56F4\u4E0E\u8D1F\u8D23\u4EBA\u4EEA\u8868\u76D8\u4FDD\u6301\u4E00\u81F4\uFF1B\u5F00\u542F\u540E\uFF0C\u53EF\u53EA\u8BFB\u67E5\u770B\u8303\u56F4\u5185\u6210\u5458\u53C2\u4E0E\u7684\u9879\u76EE\u548C\u4EFB\u52A1\u3002")))]],2),t.scopeOnly?t._e():e("div",{staticClass:"department-owner-view-switch"},[e("div",[e("strong",[t._v(t._s(t.$L("\u5728\u9879\u76EE\u5217\u8868\u4E2D\u542F\u7528")))]),e("span",{staticClass:"department-owner-view-switch-desc"},[t._v(t._s(t.$L("\u5F00\u542F\u540E\u5728\u9879\u76EE\u5217\u8868\u4E2D\u663E\u793A\u989D\u5916\u7684\u53EA\u8BFB\u9879\u76EE")))])]),e("iSwitch",{model:{value:t.draftProjectViewEnabled,callback:function(s){t.draftProjectViewEnabled=s},expression:"draftProjectViewEnabled"}})],1),t.scopeOnly||t.draftProjectViewEnabled?[t.managedDepartments.length>1?e("div",{staticClass:"department-owner-view-actions"},[e("a",{attrs:{href:"javascript:void(0)"},on:{click:function(s){t.draftIds=[]}}},[t._v(t._s(t.$L("\u6E05\u7A7A")))]),e("a",{attrs:{href:"javascript:void(0)"},on:{click:function(s){t.draftIds=t.managedDepartments.map(function(n){return n.id})}}},[t._v(t._s(t.$L("\u5168\u9009")))]),e("a",{attrs:{href:"javascript:void(0)"},on:{click:t.reverseDraft}},[t._v(t._s(t.$L("\u53CD\u9009")))])]):t._e(),e("CheckboxGroup",{staticClass:"department-owner-view-list",model:{value:t.draftIds,callback:function(s){t.draftIds=s},expression:"draftIds"}},t._l(t.managedDepartments,function(s){return e("div",{key:s.id,class:["department-owner-view-item",t.draftIds.includes(s.id)?"active":""],on:{click:function(n){return t.toggleDraft(s.id)}}},[e("div",{staticClass:"department-owner-view-icon"},[e("i",{staticClass:"taskfont"},[t._v("\uE75C")])]),e("div",{staticClass:"department-owner-view-name"},[t._v(t._s(s.name))]),e("Checkbox",{staticClass:"department-owner-view-checkbox",attrs:{label:s.id},nativeOn:{click:function(n){n.stopPropagation()}}},[e("span")])],1)}),0)]:t._e()],2),e("div",{staticClass:"adaption",attrs:{slot:"footer"},slot:"footer"},[e("Button",{attrs:{type:"default",disabled:t.applyLoading},on:{click:function(s){return t.$emit("input",!1)}}},[t._v(t._s(t.$L("\u53D6\u6D88")))]),e("Button",{attrs:{type:"primary",disabled:t.draftIds.length===0,loading:t.applyLoading},on:{click:t.apply}},[t._v(t._s(t.$L("\u786E\u5B9A")))])],1)])},l=[];const c={name:"DepartmentOwnerView",props:{value:Boolean,scopeOnly:Boolean},data(){return{draftIds:[],draftProjectViewEnabled:!1,applyLoading:!1}},computed:{...i(["userInfo","cacheDepartmentOwnerIds","departmentOwnerProjectViewEnabled"]),modalTitle(){return this.scopeOnly?this.$L("\u9009\u62E9\u56E2\u961F\u8303\u56F4"):this.$L("\u9879\u76EE\u8D1F\u8D23\u4EBA\u89C6\u89D2")},managedDepartments(){return(this.userInfo.managed_departments||[]).map(t=>({...t,id:parseInt(t.id)}))}},watch:{value:{immediate:!0,handler(t){if(t){const a=(this.cacheDepartmentOwnerIds||[]).slice();this.draftIds=a.length>0?a:this.managedDepartments.map(e=>e.id),this.draftProjectViewEnabled=this.departmentOwnerProjectViewEnabled}else this.applyLoading=!1}}},methods:{toggleDraft(t){t=parseInt(t);const a=this.draftIds.indexOf(t);a>-1?this.draftIds.splice(a,1):this.draftIds.push(t)},reverseDraft(){const t=this.draftIds.map(a=>parseInt(a));this.draftIds=this.managedDepartments.map(a=>a.id).filter(a=>!t.includes(a))},async apply(){if(!this.applyLoading){this.applyLoading=!0;try{await this.$store.dispatch("setDepartmentOwnerIds",this.scopeOnly?this.draftIds:{ids:this.draftIds,projectViewEnabled:this.draftProjectViewEnabled}),this.$emit("input",!1)}catch(t){$A.modalError((t==null?void 0:t.msg)||this.$L("\u5207\u6362\u5931\u8D25"))}finally{this.applyLoading=!1}}}}},r={};var p=d(c,o,l,!1,f,"61d09b1f",null,null);function f(t){for(let a in r)this[a]=r[a]}var u=function(){return p.exports}();export{u as D}; import{m as i}from"./vuex.cc7cb26e.js";import{n as d}from"./app.78a291d1.js";var o=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("Modal",{attrs:{value:t.value,title:t.modalTitle,"mask-closable":!1,width:"520"},on:{input:function(s){return t.$emit("input",s)}}},[e("div",{staticClass:"department-owner-view-modal"},[e("Alert",{attrs:{type:"info","show-icon":""}},[t.scopeOnly?[t._v(t._s(t.$L("\u9009\u62E9\u9700\u8981\u7EDF\u8BA1\u7684\u90E8\u95E8\uFF0C\u5305\u542B\u6240\u9009\u90E8\u95E8\u53CA\u5176\u6240\u6709\u4E0B\u7EA7\u90E8\u95E8\u3002")))]:[t._v(t._s(t.$L("\u90E8\u95E8\u8303\u56F4\u4E0E\u8D1F\u8D23\u4EBA\u4EEA\u8868\u76D8\u4FDD\u6301\u4E00\u81F4\uFF1B\u5F00\u542F\u540E\uFF0C\u53EF\u53EA\u8BFB\u67E5\u770B\u8303\u56F4\u5185\u6210\u5458\u53C2\u4E0E\u7684\u9879\u76EE\u548C\u4EFB\u52A1\u3002")))]],2),t.scopeOnly?t._e():e("div",{staticClass:"department-owner-view-switch"},[e("div",[e("strong",[t._v(t._s(t.$L("\u5728\u9879\u76EE\u5217\u8868\u4E2D\u542F\u7528")))]),e("span",{staticClass:"department-owner-view-switch-desc"},[t._v(t._s(t.$L("\u5F00\u542F\u540E\u5728\u9879\u76EE\u5217\u8868\u4E2D\u663E\u793A\u989D\u5916\u7684\u53EA\u8BFB\u9879\u76EE")))])]),e("iSwitch",{model:{value:t.draftProjectViewEnabled,callback:function(s){t.draftProjectViewEnabled=s},expression:"draftProjectViewEnabled"}})],1),t.scopeOnly||t.draftProjectViewEnabled?[t.managedDepartments.length>1?e("div",{staticClass:"department-owner-view-actions"},[e("a",{attrs:{href:"javascript:void(0)"},on:{click:function(s){t.draftIds=[]}}},[t._v(t._s(t.$L("\u6E05\u7A7A")))]),e("a",{attrs:{href:"javascript:void(0)"},on:{click:function(s){t.draftIds=t.managedDepartments.map(function(n){return n.id})}}},[t._v(t._s(t.$L("\u5168\u9009")))]),e("a",{attrs:{href:"javascript:void(0)"},on:{click:t.reverseDraft}},[t._v(t._s(t.$L("\u53CD\u9009")))])]):t._e(),e("CheckboxGroup",{staticClass:"department-owner-view-list",model:{value:t.draftIds,callback:function(s){t.draftIds=s},expression:"draftIds"}},t._l(t.managedDepartments,function(s){return e("div",{key:s.id,class:["department-owner-view-item",t.draftIds.includes(s.id)?"active":""],on:{click:function(n){return t.toggleDraft(s.id)}}},[e("div",{staticClass:"department-owner-view-icon"},[e("i",{staticClass:"taskfont"},[t._v("\uE75C")])]),e("div",{staticClass:"department-owner-view-name"},[t._v(t._s(s.name))]),e("Checkbox",{staticClass:"department-owner-view-checkbox",attrs:{label:s.id},nativeOn:{click:function(n){n.stopPropagation()}}},[e("span")])],1)}),0)]:t._e()],2),e("div",{staticClass:"adaption",attrs:{slot:"footer"},slot:"footer"},[e("Button",{attrs:{type:"default",disabled:t.applyLoading},on:{click:function(s){return t.$emit("input",!1)}}},[t._v(t._s(t.$L("\u53D6\u6D88")))]),e("Button",{attrs:{type:"primary",disabled:t.draftIds.length===0,loading:t.applyLoading},on:{click:t.apply}},[t._v(t._s(t.$L("\u786E\u5B9A")))])],1)])},l=[];const c={name:"DepartmentOwnerView",props:{value:Boolean,scopeOnly:Boolean},data(){return{draftIds:[],draftProjectViewEnabled:!1,applyLoading:!1}},computed:{...i(["userInfo","cacheDepartmentOwnerIds","departmentOwnerProjectViewEnabled"]),modalTitle(){return this.scopeOnly?this.$L("\u9009\u62E9\u56E2\u961F\u8303\u56F4"):this.$L("\u9879\u76EE\u8D1F\u8D23\u4EBA\u89C6\u89D2")},managedDepartments(){return(this.userInfo.managed_departments||[]).map(t=>({...t,id:parseInt(t.id)}))}},watch:{value:{immediate:!0,handler(t){if(t){const a=(this.cacheDepartmentOwnerIds||[]).slice();this.draftIds=a.length>0?a:this.managedDepartments.map(e=>e.id),this.draftProjectViewEnabled=this.departmentOwnerProjectViewEnabled}else this.applyLoading=!1}}},methods:{toggleDraft(t){t=parseInt(t);const a=this.draftIds.indexOf(t);a>-1?this.draftIds.splice(a,1):this.draftIds.push(t)},reverseDraft(){const t=this.draftIds.map(a=>parseInt(a));this.draftIds=this.managedDepartments.map(a=>a.id).filter(a=>!t.includes(a))},async apply(){if(!this.applyLoading){this.applyLoading=!0;try{await this.$store.dispatch("setDepartmentOwnerIds",this.scopeOnly?this.draftIds:{ids:this.draftIds,projectViewEnabled:this.draftProjectViewEnabled}),this.$emit("input",!1)}catch(t){$A.modalError((t==null?void 0:t.msg)||this.$L("\u5207\u6362\u5931\u8D25"))}finally{this.applyLoading=!1}}}}},r={};var p=d(c,o,l,!1,f,"61d09b1f",null,null);function f(t){for(let a in r)this[a]=r[a]}var u=function(){return p.exports}();export{u as D};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{m as s}from"./vuex.cc7cb26e.js";import{I as m}from"./IFrame.cb3685ad.js";import{n as p,l as o}from"./app.d80882e4.js";import"./jquery.ead8aad7.js";import"./@babel.9410f858.js";import"./dayjs.2db954dd.js";import"./localforage.44d37169.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var l=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"drawio-content"},[i("IFrame",{ref:"frame",staticClass:"drawio-iframe",attrs:{src:t.url},on:{"on-message":t.onMessage}}),t.loadIng?i("div",{staticClass:"drawio-loading"},[i("Loading")],1):t._e()],1)},d=[];const u={name:"Drawio",components:{IFrame:m},props:{value:{type:Object,default:function(){return{}}},title:{type:String,default:""},readOnly:{type:Boolean,default:!1}},data(){return{loadIng:!0,url:null,bakData:""}},created(){let t=o;switch(o){case"zh-CHT":t="zh-tw";break}let e=this.readOnly?1:0,i=this.readOnly?0:1,n=this.themeName==="dark"?"dark":"kennedy",r=`?title=${this.title?encodeURIComponent(this.title):""}&chrome=${i}&lightbox=${e}&ui=${n}&lang=${t}&offline=1&pwa=0&embed=1&noLangIcon=1&noExitBtn=1&noSaveBtn=1&saveAndExit=0&spin=1&proto=json`;this.$Electron?this.url=$A.originUrl(`drawio/webapp/index.html${r}`):this.url=$A.mainUrl(`drawio/webapp/${r}`)},mounted(){window.addEventListener("message",this.handleMessage)},beforeDestroy(){window.removeEventListener("message",this.handleMessage)},watch:{value:{handler(t){this.bakData!=$A.jsonStringify(t)&&(this.bakData=$A.jsonStringify(t),this.updateContent())},deep:!0}},computed:{...s(["themeName"])},methods:{formatZoom(t){return t+"%"},updateContent(){this.$refs.frame.postMessage(JSON.stringify({action:"load",autosave:1,xml:this.value.xml}))},onMessage(t){switch(t.event){case"init":this.loadIng=!1,this.updateContent();break;case"load":typeof this.value.xml=="undefined"&&this.$refs.frame.postMessage(JSON.stringify({action:"template"}));break;case"autosave":const e={xml:t.xml};this.bakData=$A.jsonStringify(e),this.$emit("input",e);break;case"save":this.$emit("saveData");break}}}},a={};var c=p(u,l,d,!1,h,"86e491c2",null,null);function h(t){for(let e in a)this[e]=a[e]}var pt=function(){return c.exports}();export{pt as default}; import{m as s}from"./vuex.cc7cb26e.js";import{I as m}from"./IFrame.551ecc40.js";import{n as p,l as o}from"./app.78a291d1.js";import"./jquery.9174f09e.js";import"./@babel.9410f858.js";import"./dayjs.ce1ced10.js";import"./localforage.8ee247f7.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var l=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"drawio-content"},[i("IFrame",{ref:"frame",staticClass:"drawio-iframe",attrs:{src:t.url},on:{"on-message":t.onMessage}}),t.loadIng?i("div",{staticClass:"drawio-loading"},[i("Loading")],1):t._e()],1)},d=[];const u={name:"Drawio",components:{IFrame:m},props:{value:{type:Object,default:function(){return{}}},title:{type:String,default:""},readOnly:{type:Boolean,default:!1}},data(){return{loadIng:!0,url:null,bakData:""}},created(){let t=o;switch(o){case"zh-CHT":t="zh-tw";break}let e=this.readOnly?1:0,i=this.readOnly?0:1,n=this.themeName==="dark"?"dark":"kennedy",r=`?title=${this.title?encodeURIComponent(this.title):""}&chrome=${i}&lightbox=${e}&ui=${n}&lang=${t}&offline=1&pwa=0&embed=1&noLangIcon=1&noExitBtn=1&noSaveBtn=1&saveAndExit=0&spin=1&proto=json`;this.$Electron?this.url=$A.originUrl(`drawio/webapp/index.html${r}`):this.url=$A.mainUrl(`drawio/webapp/${r}`)},mounted(){window.addEventListener("message",this.handleMessage)},beforeDestroy(){window.removeEventListener("message",this.handleMessage)},watch:{value:{handler(t){this.bakData!=$A.jsonStringify(t)&&(this.bakData=$A.jsonStringify(t),this.updateContent())},deep:!0}},computed:{...s(["themeName"])},methods:{formatZoom(t){return t+"%"},updateContent(){this.$refs.frame.postMessage(JSON.stringify({action:"load",autosave:1,xml:this.value.xml}))},onMessage(t){switch(t.event){case"init":this.loadIng=!1,this.updateContent();break;case"load":typeof this.value.xml=="undefined"&&this.$refs.frame.postMessage(JSON.stringify({action:"template"}));break;case"autosave":const e={xml:t.xml};this.bakData=$A.jsonStringify(e),this.$emit("input",e);break;case"save":this.$emit("saveData");break}}}},a={};var c=p(u,l,d,!1,h,"86e491c2",null,null);function h(t){for(let e in a)this[e]=a[e]}var pt=function(){return c.exports}();export{pt as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{n}from"./app.d80882e4.js";var i=function(){var e=this,s=e.$createElement,r=e._self._c||s;return r("iframe",{directives:[{name:"show",rawName:"v-show",value:e.src,expression:"src"}],ref:"iframe",attrs:{src:e.src}})},a=[];const o={name:"IFrame",props:{src:{type:String,default:""}},mounted(){this.$refs.iframe.addEventListener("load",this.handleLoad),window.addEventListener("message",this.handleMessage)},beforeDestroy(){this.$refs.iframe.removeEventListener("load",this.handleLoad),window.removeEventListener("message",this.handleMessage)},methods:{handleLoad(){this.$emit("on-load")},handleMessage({data:e,source:s}){var r;s===((r=this.$refs.iframe)==null?void 0:r.contentWindow)&&(e=$A.jsonParse(e),e.source==="fileView"&&e.action==="picture"&&this.$store.dispatch("previewImage",{index:e.params.index,list:e.params.array}),this.$emit("on-message",e))},postMessage(e,s="*"){this.$refs.iframe&&this.$refs.iframe.contentWindow.postMessage(e,s)}}},t={};var m=n(o,i,a,!1,c,null,null,null);function c(e){for(let s in t)this[s]=t[s]}var l=function(){return m.exports}();export{l as I}; import{n}from"./app.78a291d1.js";var i=function(){var e=this,s=e.$createElement,r=e._self._c||s;return r("iframe",{directives:[{name:"show",rawName:"v-show",value:e.src,expression:"src"}],ref:"iframe",attrs:{src:e.src}})},a=[];const o={name:"IFrame",props:{src:{type:String,default:""}},mounted(){this.$refs.iframe.addEventListener("load",this.handleLoad),window.addEventListener("message",this.handleMessage)},beforeDestroy(){this.$refs.iframe.removeEventListener("load",this.handleLoad),window.removeEventListener("message",this.handleMessage)},methods:{handleLoad(){this.$emit("on-load")},handleMessage({data:e,source:s}){var r;s===((r=this.$refs.iframe)==null?void 0:r.contentWindow)&&(e=$A.jsonParse(e),e.source==="fileView"&&e.action==="picture"&&this.$store.dispatch("previewImage",{index:e.params.index,list:e.params.array}),this.$emit("on-message",e))},postMessage(e,s="*"){this.$refs.iframe&&this.$refs.iframe.contentWindow.postMessage(e,s)}}},t={};var m=n(o,i,a,!1,c,null,null,null);function c(e){for(let s in t)this[s]=t[s]}var l=function(){return m.exports}();export{l as I};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{n as r}from"./app.d80882e4.js";var a=function(){var t=this,n=t.$createElement,e=t._self._c||n;return t.windowTouch?e("div",[e("Button",{attrs:{loading:t.loading,type:"primary",icon:"ios-search"},on:{click:t.onSearch}},[t._v(t._s(t.$L("\u641C\u7D22")))]),t.filtering?e("Button",{attrs:{type:"text"},on:{click:t.onCancelFilter}},[t._v(t._s(t.$L("\u53D6\u6D88\u7B5B\u9009")))]):e("Button",{attrs:{loading:t.loading,type:"text",icon:"md-refresh"},on:{click:t.onRefresh}},[t._v(t._s(t.$L("\u5237\u65B0")))])],1):e("Tooltip",{attrs:{theme:"light",placement:t.placement,"transfer-class-name":"search-button-clear",transfer:""}},[e("Button",{attrs:{loading:t.loading,type:"primary",icon:"ios-search"},on:{click:t.onSearch}},[t._v(t._s(t.$L("\u641C\u7D22")))]),e("div",{attrs:{slot:"content"},slot:"content"},[t.filtering?e("Button",{attrs:{type:"text"},on:{click:t.onCancelFilter}},[t._v(t._s(t.$L("\u53D6\u6D88\u7B5B\u9009")))]):e("Button",{attrs:{loading:t.loading,type:"text"},on:{click:t.onRefresh}},[t._v(t._s(t.$L("\u5237\u65B0")))])],1)],1)},i=[];const l={name:"SearchButton",props:{loading:{type:Boolean,default:!1},filtering:{type:Boolean,default:!1},placement:{type:String,default:"bottom"}},methods:{onSearch(){this.$emit("search")},onRefresh(){this.$emit("refresh")},onCancelFilter(){this.$emit("cancelFilter")}}},o={};var s=r(l,a,i,!1,c,null,null,null);function c(t){for(let n in o)this[n]=o[n]}var h=function(){return s.exports}();export{h as S}; import{n as r}from"./app.78a291d1.js";var a=function(){var t=this,n=t.$createElement,e=t._self._c||n;return t.windowTouch?e("div",[e("Button",{attrs:{loading:t.loading,type:"primary",icon:"ios-search"},on:{click:t.onSearch}},[t._v(t._s(t.$L("\u641C\u7D22")))]),t.filtering?e("Button",{attrs:{type:"text"},on:{click:t.onCancelFilter}},[t._v(t._s(t.$L("\u53D6\u6D88\u7B5B\u9009")))]):e("Button",{attrs:{loading:t.loading,type:"text",icon:"md-refresh"},on:{click:t.onRefresh}},[t._v(t._s(t.$L("\u5237\u65B0")))])],1):e("Tooltip",{attrs:{theme:"light",placement:t.placement,"transfer-class-name":"search-button-clear",transfer:""}},[e("Button",{attrs:{loading:t.loading,type:"primary",icon:"ios-search"},on:{click:t.onSearch}},[t._v(t._s(t.$L("\u641C\u7D22")))]),e("div",{attrs:{slot:"content"},slot:"content"},[t.filtering?e("Button",{attrs:{type:"text"},on:{click:t.onCancelFilter}},[t._v(t._s(t.$L("\u53D6\u6D88\u7B5B\u9009")))]):e("Button",{attrs:{loading:t.loading,type:"text"},on:{click:t.onRefresh}},[t._v(t._s(t.$L("\u5237\u65B0")))])],1)],1)},i=[];const l={name:"SearchButton",props:{loading:{type:Boolean,default:!1},filtering:{type:Boolean,default:!1},placement:{type:String,default:"bottom"}},methods:{onSearch(){this.$emit("search")},onRefresh(){this.$emit("refresh")},onCancelFilter(){this.$emit("cancelFilter")}}},o={};var s=r(l,a,i,!1,c,null,null,null);function c(t){for(let n in o)this[n]=o[n]}var h=function(){return s.exports}();export{h as S};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

7
public/js/build/app.4744fa3b.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{m}from"./vuex.cc7cb26e.js";import{M as e}from"./index.505d17fd.js";import{n as a}from"./app.d80882e4.js";import"./vue.adba9046.js";import"./@babel.9410f858.js";import"./view-design-hi.24419422.js";import"./@micro-zoe.39406924.js";import"./DialogWrapper.2be94133.js";import"./chunkedUpload.59d4149a.js";import"./axios.37c7f908.js";import"./spark-md5.0443325f.js";import"./index.7452a39e.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.444ba02a.js";import"./webhook.378987f3.js";import"./jquery.ead8aad7.js";import"./dayjs.2db954dd.js";import"./localforage.44d37169.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./openpgp_hi.15f91b1d.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./html-to-md.f297036e.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var n=function(){var t=this,o=t.$createElement,r=t._self._c||o;return r("MicroApps",{ref:"app",attrs:{"window-type":"popout"}})},s=[];const u={components:{MicroApps:e},computed:{...m(["userIsAdmin"])},async mounted(){const{name:t}=this.$route.params;if(!t){$A.modalError("\u5E94\u7528\u4E0D\u5B58\u5728");return}if(t==="iframe-test"){if(!this.userIsAdmin){$A.modalError("\u4EC5\u7BA1\u7406\u5458\u53EF\u4F7F\u7528\u6B64\u529F\u80FD");return}let{url:r}=this.$route.query;if(!r){if(r=await this.promptIframeUrl(),!r)return;this.$router.replace({path:this.$route.path,query:{...this.$route.query,url:r}}).catch(()=>{})}await this.$refs.app.onOpen({id:"iframe-test",name:"iframe-test",url:r,type:"iframe",transparent:!0,keep_alive:!1});return}const o=(await $A.IDBArray("cacheMicroApps")).reverse().find(r=>r.name===t);if(!o){$A.modalError("\u5E94\u7528\u4E0D\u5B58\u5728");return}await this.$refs.app.onOpen(o)},methods:{promptIframeUrl(){return new Promise((t,o)=>{$A.modalInput({title:this.$L("\u8BF7\u8F93\u5165 URL"),placeholder:"https://example.com",onOk:r=>{const i=(r||"").trim();if(!i)return this.$L("URL\u4E0D\u80FD\u4E3A\u7A7A");t(i)},onCancel:()=>o()})}).catch(()=>null)}}},p={};var c=a(u,n,s,!1,l,null,null,null);function l(t){for(let o in p)this[o]=p[o]}var hr=function(){return c.exports}();export{hr as default};

1
public/js/build/apps.32d67b51.js vendored Normal file
View File

@ -0,0 +1 @@
import{m}from"./vuex.cc7cb26e.js";import{M as e}from"./index.1083c0cc.js";import{n as a}from"./app.78a291d1.js";import"./vue.adba9046.js";import"./@babel.9410f858.js";import"./view-design-hi.24419422.js";import"./@micro-zoe.39406924.js";import"./DialogWrapper.66e32264.js";import"./chunkedUpload.5d2e8ab1.js";import"./axios.37c7f908.js";import"./spark-md5.0443325f.js";import"./index.c4ab8fcd.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.d37939e1.js";import"./resize-observer.9ad6b159.js";import"./webhook.378987f3.js";import"./jquery.9174f09e.js";import"./dayjs.ce1ced10.js";import"./localforage.8ee247f7.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./openpgp_hi.15f91b1d.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./html-to-md.f297036e.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var n=function(){var t=this,o=t.$createElement,r=t._self._c||o;return r("MicroApps",{ref:"app",attrs:{"window-type":"popout"}})},s=[];const u={components:{MicroApps:e},computed:{...m(["userIsAdmin"])},async mounted(){const{name:t}=this.$route.params;if(!t){$A.modalError("\u5E94\u7528\u4E0D\u5B58\u5728");return}if(t==="iframe-test"){if(!this.userIsAdmin){$A.modalError("\u4EC5\u7BA1\u7406\u5458\u53EF\u4F7F\u7528\u6B64\u529F\u80FD");return}let{url:r}=this.$route.query;if(!r){if(r=await this.promptIframeUrl(),!r)return;this.$router.replace({path:this.$route.path,query:{...this.$route.query,url:r}}).catch(()=>{})}await this.$refs.app.onOpen({id:"iframe-test",name:"iframe-test",url:r,type:"iframe",transparent:!0,keep_alive:!1});return}const o=(await $A.IDBArray("cacheMicroApps")).reverse().find(r=>r.name===t);if(!o){$A.modalError("\u5E94\u7528\u4E0D\u5B58\u5728");return}await this.$refs.app.onOpen(o)},methods:{promptIframeUrl(){return new Promise((t,o)=>{$A.modalInput({title:this.$L("\u8BF7\u8F93\u5165 URL"),placeholder:"https://example.com",onOk:r=>{const i=(r||"").trim();if(!i)return this.$L("URL\u4E0D\u80FD\u4E3A\u7A7A");t(i)},onCancel:()=>o()})}).catch(()=>null)}}},p={};var c=a(u,n,s,!1,l,null,null,null);function l(t){for(let o in p)this[o]=p[o]}var _r=function(){return c.exports}();export{_r as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{a as k}from"./axios.37c7f908.js";import{S as x}from"./spark-md5.0443325f.js";import{s as D,l as O}from"./app.d80882e4.js";const j=10*1024*1024,U=3,M=3,C="chunked_upload:",P=new Set(["\u4E0A\u4F20\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F","\u4E0A\u4F20\u4F1A\u8BDD\u5F52\u5C5E\u9519\u8BEF","\u5206\u7247\u5E8F\u53F7\u8D85\u51FA\u8303\u56F4","\u5206\u7247\u5927\u5C0F\u4E0D\u7B26\u5408\u9884\u671F","\u672B\u5C3E\u5206\u7247\u5927\u5C0F\u4E0D\u7B26\u5408\u9884\u671F"]);function H(t,e){return t==="hashing"?Math.round(e*.05):t==="uploading"?5+Math.round(e*.9):95+Math.round(e*.05)}async function G({file:t,scene:e,sceneParams:d={},onProgress:a,onStart:i,signal:r}){const n=typeof a=="function"?a:()=>{},o=(s,h)=>n(H(s,h),s);o("hashing",0);const w=await B(t,r,(s,h)=>{o("hashing",Math.min(99,Math.round(s/h*100)))});if(o("hashing",100),r&&r.aborted)throw new Error("aborted");const c=await S("upload/init",{hash:w,size:t.size,name:t.name,scene:e,scene_params:d},r);if(c.ret!==1)throw new Error(c.msg||"\u4E0A\u4F20\u521D\u59CB\u5316\u5931\u8D25");if(c.data&&c.data.done)return o("uploading",100),o("merging",100),c.data;const{upload_id:p,chunk_size:l,chunk_count:g,received:A}=c.data;if(F(w,p,e),typeof i=="function")try{i(p)}catch{}const y=new Set((A||[]).map(Number)),f=[];for(let s=0;s<g;s++)y.has(s)||f.push(s);let E=y.size;const b=()=>{o("uploading",Math.min(99,Math.round(E/g*100)))};b();let N=0,u=null;const z=async()=>{for(;;){if(u)return;if(r&&r.aborted){u=new Error("aborted");return}const s=N++;if(s>=f.length)return;const h=f[s],_=h*l,L=Math.min(_+l,t.size),T=t.slice(_,L);try{await $(p,h,T,r),E++,b()}catch(v){u||(u=v);return}}},I=Math.min(U,f.length||1);if(await Promise.all(Array.from({length:I},()=>z())),u)throw u;o("uploading",100),o("merging",0);const m=await S("upload/merge",{upload_id:p},r);if(m.ret!==1)throw new Error(m.msg||"\u5408\u5E76\u5931\u8D25");return o("merging",100),X(w),m.data}async function $(t,e,d,a){let i;for(let r=0;r<=M;r++){if(a&&a.aborted)throw new Error("aborted");try{const n=new FormData;n.append("upload_id",t),n.append("index",String(e)),n.append("blob",d);const o=await k.post(window.$A.apiUrl("upload/chunk"),n,{headers:R(),signal:a});if(o.data&&o.data.ret===1)return o.data.data;i=new Error(o.data&&o.data.msg||"\u5206\u7247\u4E0A\u4F20\u5931\u8D25")}catch(n){if(a&&a.aborted)throw n;i=n}if(P.has(i.message))throw i;r<M&&await new Promise(n=>setTimeout(n,500*Math.pow(2,r)))}throw i||new Error("\u5206\u7247\u91CD\u8BD5\u8017\u5C3D")}async function S(t,e,d){return(await k.post(window.$A.apiUrl(t),e,{headers:{...R(),"Content-Type":"application/json"},signal:d})).data}function R(){return{token:D.state.userToken,language:O,platform:window.$A.Platform,version:window.systemInfo&&window.systemInfo.version||"0.0.1",fd:window.$A.getSessionStorageString("userWsFd")}}async function B(t,e,d){const a=new x.ArrayBuffer,i=2*1024*1024;for(let r=0;r<t.size;r+=i){if(e&&e.aborted)throw new Error("aborted");const n=Math.min(r+i,t.size),o=await t.slice(r,n).arrayBuffer();a.append(o),d(n,t.size),await new Promise(w=>setTimeout(w,0))}return a.end().toLowerCase()}function F(t,e,d){try{const a={upload_id:e,scene:d,t:Date.now()};localStorage.setItem(C+t,JSON.stringify(a))}catch{}}function X(t){try{localStorage.removeItem(C+t)}catch{}}export{j as C,G as c}; import{a as k}from"./axios.37c7f908.js";import{S as x}from"./spark-md5.0443325f.js";import{s as D,l as O}from"./app.78a291d1.js";const j=10*1024*1024,U=3,M=3,C="chunked_upload:",P=new Set(["\u4E0A\u4F20\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F","\u4E0A\u4F20\u4F1A\u8BDD\u5F52\u5C5E\u9519\u8BEF","\u5206\u7247\u5E8F\u53F7\u8D85\u51FA\u8303\u56F4","\u5206\u7247\u5927\u5C0F\u4E0D\u7B26\u5408\u9884\u671F","\u672B\u5C3E\u5206\u7247\u5927\u5C0F\u4E0D\u7B26\u5408\u9884\u671F"]);function H(t,e){return t==="hashing"?Math.round(e*.05):t==="uploading"?5+Math.round(e*.9):95+Math.round(e*.05)}async function G({file:t,scene:e,sceneParams:d={},onProgress:a,onStart:i,signal:r}){const n=typeof a=="function"?a:()=>{},o=(s,h)=>n(H(s,h),s);o("hashing",0);const w=await B(t,r,(s,h)=>{o("hashing",Math.min(99,Math.round(s/h*100)))});if(o("hashing",100),r&&r.aborted)throw new Error("aborted");const c=await S("upload/init",{hash:w,size:t.size,name:t.name,scene:e,scene_params:d},r);if(c.ret!==1)throw new Error(c.msg||"\u4E0A\u4F20\u521D\u59CB\u5316\u5931\u8D25");if(c.data&&c.data.done)return o("uploading",100),o("merging",100),c.data;const{upload_id:p,chunk_size:l,chunk_count:g,received:A}=c.data;if(F(w,p,e),typeof i=="function")try{i(p)}catch{}const y=new Set((A||[]).map(Number)),f=[];for(let s=0;s<g;s++)y.has(s)||f.push(s);let E=y.size;const b=()=>{o("uploading",Math.min(99,Math.round(E/g*100)))};b();let N=0,u=null;const z=async()=>{for(;;){if(u)return;if(r&&r.aborted){u=new Error("aborted");return}const s=N++;if(s>=f.length)return;const h=f[s],_=h*l,L=Math.min(_+l,t.size),T=t.slice(_,L);try{await $(p,h,T,r),E++,b()}catch(v){u||(u=v);return}}},I=Math.min(U,f.length||1);if(await Promise.all(Array.from({length:I},()=>z())),u)throw u;o("uploading",100),o("merging",0);const m=await S("upload/merge",{upload_id:p},r);if(m.ret!==1)throw new Error(m.msg||"\u5408\u5E76\u5931\u8D25");return o("merging",100),X(w),m.data}async function $(t,e,d,a){let i;for(let r=0;r<=M;r++){if(a&&a.aborted)throw new Error("aborted");try{const n=new FormData;n.append("upload_id",t),n.append("index",String(e)),n.append("blob",d);const o=await k.post(window.$A.apiUrl("upload/chunk"),n,{headers:R(),signal:a});if(o.data&&o.data.ret===1)return o.data.data;i=new Error(o.data&&o.data.msg||"\u5206\u7247\u4E0A\u4F20\u5931\u8D25")}catch(n){if(a&&a.aborted)throw n;i=n}if(P.has(i.message))throw i;r<M&&await new Promise(n=>setTimeout(n,500*Math.pow(2,r)))}throw i||new Error("\u5206\u7247\u91CD\u8BD5\u8017\u5C3D")}async function S(t,e,d){return(await k.post(window.$A.apiUrl(t),e,{headers:{...R(),"Content-Type":"application/json"},signal:d})).data}function R(){return{token:D.state.userToken,language:O,platform:window.$A.Platform,version:window.systemInfo&&window.systemInfo.version||"0.0.1",fd:window.$A.getSessionStorageString("userWsFd")}}async function B(t,e,d){const a=new x.ArrayBuffer,i=2*1024*1024;for(let r=0;r<t.size;r+=i){if(e&&e.aborted)throw new Error("aborted");const n=Math.min(r+i,t.size),o=await t.slice(r,n).arrayBuffer();a.append(o),d(n,t.size),await new Promise(w=>setTimeout(w,0))}return a.end().toLowerCase()}function F(t,e,d){try{const a={upload_id:e,scene:d,t:Date.now()};localStorage.setItem(C+t,JSON.stringify(a))}catch{}}function X(t){try{localStorage.removeItem(C+t)}catch{}}export{j as C,G as c};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{n as l}from"./app.d80882e4.js";import"./jquery.ead8aad7.js";import"./@babel.9410f858.js";import"./dayjs.2db954dd.js";import"./localforage.44d37169.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var m=function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"setting-device"},[i("ul",[t.loadIng>0&&t.devices.length===0?i("li",{staticClass:"loading"},[i("Loading")],1):t._l(t.devices,function(e){return i("li",{key:e.id},[i("div",{staticClass:"icon"},[i("span",{class:t.getIcon(e.detail)})]),i("div",{staticClass:"info"},[i("div",{staticClass:"title"},[i("span",{staticClass:"name"},[t._v(t._s(t.getName(e.detail)))]),i("span",{staticClass:"device"},[t._v(t._s(t.getOs(e.detail)))])]),i("div",{staticClass:"time"},[i("EPopover",{attrs:{placement:"bottom-start",trigger:"click"}},[i("div",{staticClass:"setting-device-popover"},[i("p",[t._v(t._s(t.$L("\u767B\u5F55\u65F6\u95F4"))+": "+t._s(e.created_at))]),i("p",[t._v(t._s(t.$L("\u66F4\u65B0\u65F6\u95F4"))+": "+t._s(e.updated_at))]),i("p",[t._v(t._s(t.$L("\u8FC7\u671F\u65F6\u95F4"))+": "+t._s(e.expired_at))])]),i("span",{attrs:{slot:"reference"},slot:"reference"},[t._v(t._s(e.updated_at))])])],1)]),i("div",[e.is_current?i("span",{staticClass:"current"},[t._v(t._s(t.$L("\u5F53\u524D\u8BBE\u5907")))]):i("Button",{on:{click:function(o){return t.onLogout(e)}}},[t._v(t._s(t.$L("\u9000\u51FA\u767B\u5F55")))])],1)])})],2)])},p=[];const c={name:"SettingDevice",data(){return{loadIng:0,devices:[]}},mounted(){this.getDeviceList()},methods:{getDeviceList(){this.loadIng++,this.$store.dispatch("call",{url:"users/device/list"}).then(({data:t})=>{this.devices=t.list,typeof this.$parent.updateDeviceCount=="function"&&this.$parent.updateDeviceCount(this.devices.length)}).catch(({msg:t})=>{$A.modalError(t),this.devices=[]}).finally(()=>{this.loadIng--})},getIcon({app_type:t,app_name:r}){return/ios/i.test(t)?/ipad/i.test(r)?"tablet":/iphone/i.test(r)?"phone":"apple":/android/i.test(t)?/(tablet|phablet)/i.test(r)?"tablet":"android":/mac/i.test(t)?"macos":/win/i.test(t)?"window":"web"},getName({app_brand:t,app_model:r,device_name:i,app_type:e,app_name:o,browser:a}){const s=[];if(/web/i.test(e))s.push(a,this.$L("\u6D4F\u89C8\u5668"));else{if(i)return i;t?s.push(t,r):s.push(o||e,this.$L("\u5BA2\u6237\u7AEF"))}return s.join(" ")},getOs({app_os:t,os:r}){return t||r},onLogout(t){$A.modalConfirm({title:"\u9000\u51FA\u767B\u5F55",content:"\u662F\u5426\u5728\u8BE5\u8BBE\u5907\u4E0A\u9000\u51FA\u767B\u5F55\uFF1F",loading:!0,onOk:()=>new Promise((r,i)=>{this.$store.dispatch("call",{url:"users/device/logout",data:{id:t.id}}).then(({msg:e})=>{r(e),this.getDeviceList()}).catch(({msg:e})=>{i(e)})})})}}},n={};var u=l(c,m,p,!1,d,null,null,null);function d(t){for(let r in n)this[r]=n[r]}var nt=function(){return u.exports}();export{nt as default}; import{n as l}from"./app.78a291d1.js";import"./jquery.9174f09e.js";import"./@babel.9410f858.js";import"./dayjs.ce1ced10.js";import"./localforage.8ee247f7.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var m=function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"setting-device"},[i("ul",[t.loadIng>0&&t.devices.length===0?i("li",{staticClass:"loading"},[i("Loading")],1):t._l(t.devices,function(e){return i("li",{key:e.id},[i("div",{staticClass:"icon"},[i("span",{class:t.getIcon(e.detail)})]),i("div",{staticClass:"info"},[i("div",{staticClass:"title"},[i("span",{staticClass:"name"},[t._v(t._s(t.getName(e.detail)))]),i("span",{staticClass:"device"},[t._v(t._s(t.getOs(e.detail)))])]),i("div",{staticClass:"time"},[i("EPopover",{attrs:{placement:"bottom-start",trigger:"click"}},[i("div",{staticClass:"setting-device-popover"},[i("p",[t._v(t._s(t.$L("\u767B\u5F55\u65F6\u95F4"))+": "+t._s(e.created_at))]),i("p",[t._v(t._s(t.$L("\u66F4\u65B0\u65F6\u95F4"))+": "+t._s(e.updated_at))]),i("p",[t._v(t._s(t.$L("\u8FC7\u671F\u65F6\u95F4"))+": "+t._s(e.expired_at))])]),i("span",{attrs:{slot:"reference"},slot:"reference"},[t._v(t._s(e.updated_at))])])],1)]),i("div",[e.is_current?i("span",{staticClass:"current"},[t._v(t._s(t.$L("\u5F53\u524D\u8BBE\u5907")))]):i("Button",{on:{click:function(o){return t.onLogout(e)}}},[t._v(t._s(t.$L("\u9000\u51FA\u767B\u5F55")))])],1)])})],2)])},p=[];const c={name:"SettingDevice",data(){return{loadIng:0,devices:[]}},mounted(){this.getDeviceList()},methods:{getDeviceList(){this.loadIng++,this.$store.dispatch("call",{url:"users/device/list"}).then(({data:t})=>{this.devices=t.list,typeof this.$parent.updateDeviceCount=="function"&&this.$parent.updateDeviceCount(this.devices.length)}).catch(({msg:t})=>{$A.modalError(t),this.devices=[]}).finally(()=>{this.loadIng--})},getIcon({app_type:t,app_name:r}){return/ios/i.test(t)?/ipad/i.test(r)?"tablet":/iphone/i.test(r)?"phone":"apple":/android/i.test(t)?/(tablet|phablet)/i.test(r)?"tablet":"android":/mac/i.test(t)?"macos":/win/i.test(t)?"window":"web"},getName({app_brand:t,app_model:r,device_name:i,app_type:e,app_name:o,browser:a}){const s=[];if(/web/i.test(e))s.push(a,this.$L("\u6D4F\u89C8\u5668"));else{if(i)return i;t?s.push(t,r):s.push(o||e,this.$L("\u5BA2\u6237\u7AEF"))}return s.join(" ")},getOs({app_os:t,os:r}){return t||r},onLogout(t){$A.modalConfirm({title:"\u9000\u51FA\u767B\u5F55",content:"\u662F\u5426\u5728\u8BE5\u8BBE\u5907\u4E0A\u9000\u51FA\u767B\u5F55\uFF1F",loading:!0,onOk:()=>new Promise((r,i)=>{this.$store.dispatch("call",{url:"users/device/logout",data:{id:t.id}}).then(({msg:e})=>{r(e),this.getDeviceList()}).catch(({msg:e})=>{i(e)})})})}}},n={};var u=l(c,m,p,!1,d,null,null,null);function d(t){for(let r in n)this[r]=n[r]}var nt=function(){return u.exports}();export{nt as default};

View File

@ -1 +0,0 @@
import{D as p}from"./DialogWrapper.2be94133.js";import{m}from"./vuex.cc7cb26e.js";import{n as a}from"./app.d80882e4.js";import"./chunkedUpload.59d4149a.js";import"./axios.37c7f908.js";import"./spark-md5.0443325f.js";import"./index.7452a39e.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./@babel.9410f858.js";import"./vue.adba9046.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.444ba02a.js";import"./webhook.378987f3.js";import"./jquery.ead8aad7.js";import"./dayjs.2db954dd.js";import"./localforage.44d37169.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./openpgp_hi.15f91b1d.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var e=function(){var t=this,o=t.$createElement,r=t._self._c||o;return r("div",{staticClass:"electron-dialog"},[r("PageTitle",{attrs:{title:t.dialogData.name}}),t.dialogId>0?r("DialogWrapper",{attrs:{dialogId:t.dialogId}}):t._e()],1)},n=[];const s={components:{DialogWrapper:p},computed:{...m(["cacheDialogs"]),dialogId(){const{dialogId:t}=this.$route.params;return parseInt(/^\d+$/.test(t)?t:0)},dialogData(){return this.cacheDialogs.find(({id:t})=>t===this.dialogId)||{}}}},i={};var l=a(s,e,n,!1,d,"4f6d7c8a",null,null);function d(t){for(let o in i)this[o]=i[o]}var dt=function(){return l.exports}();export{dt as default};

1
public/js/build/dialog.b2b7a24f.js vendored Normal file
View File

@ -0,0 +1 @@
import{D as p}from"./DialogWrapper.66e32264.js";import{m}from"./vuex.cc7cb26e.js";import{n as a}from"./app.78a291d1.js";import"./chunkedUpload.5d2e8ab1.js";import"./axios.37c7f908.js";import"./spark-md5.0443325f.js";import"./index.c4ab8fcd.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./@babel.9410f858.js";import"./vue.adba9046.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.d37939e1.js";import"./resize-observer.9ad6b159.js";import"./webhook.378987f3.js";import"./jquery.9174f09e.js";import"./dayjs.ce1ced10.js";import"./localforage.8ee247f7.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./openpgp_hi.15f91b1d.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var e=function(){var t=this,o=t.$createElement,r=t._self._c||o;return r("div",{staticClass:"electron-dialog"},[r("PageTitle",{attrs:{title:t.dialogData.name}}),t.dialogId>0?r("DialogWrapper",{attrs:{dialogId:t.dialogId}}):t._e()],1)},n=[];const s={components:{DialogWrapper:p},computed:{...m(["cacheDialogs"]),dialogId(){const{dialogId:t}=this.$route.params;return parseInt(/^\d+$/.test(t)?t:0)},dialogData(){return this.cacheDialogs.find(({id:t})=>t===this.dialogId)||{}}}},i={};var l=a(s,e,n,!1,d,"4f6d7c8a",null,null);function d(t){for(let o in i)this[o]=i[o]}var _t=function(){return l.exports}();export{_t as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/js/build/file.02453831.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import n from"./FileContent.66aaf547.js";import m from"./FilePreview.5d726de9.js";import{n as l}from"./app.d80882e4.js";import"./openpgp_hi.15f91b1d.js";import"./IFrame.cb3685ad.js";import"./jquery.ead8aad7.js";import"./@babel.9410f858.js";import"./dayjs.2db954dd.js";import"./localforage.44d37169.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var s=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"single-file"},[i("PageTitle",{attrs:{title:t.pageName}}),t.loadIng>0?i("Loading"):t.fileInfo?[t.isPreview?i("FilePreview",{attrs:{code:t.code,file:t.fileInfo,historyId:t.historyId,headerShow:!t.$isEEUIApp}}):i("FileContent",{attrs:{file:t.fileInfo},model:{value:t.fileShow,callback:function(r){t.fileShow=r},expression:"fileShow"}})]:t._e()],2)},p=[];const a={components:{FilePreview:m,FileContent:n},data(){return{loadIng:0,code:null,fileShow:!0,fileInfo:null}},mounted(){},computed:{historyId(){return this.$route.query?$A.runNum(this.$route.query.history_id):0},isPreview(){return this.windowPortrait||this.code||this.historyId>0||this.fileInfo&&this.fileInfo.permission===0},pageName(){return this.$route.query&&this.$route.query.history_title?this.$route.query.history_title:this.fileInfo?`${this.fileInfo.name} [${this.fileInfo.created_at}]`:""}},watch:{$route:{handler(){this.getInfo()},immediate:!0}},methods:{getInfo(){let{codeOrFileId:t}=this.$route.params,e={id:t};if(/^\d+$/.test(t))this.code=null;else if(t)this.code=t;else return;setTimeout(i=>{this.loadIng++},600),this.$store.dispatch("call",{url:"file/one",data:e}).then(({data:i})=>{this.fileInfo=i}).catch(({msg:i})=>{$A.modalError({content:i,onOk:()=>{window.close()}})}).finally(i=>{this.loadIng--})}}},o={};var f=l(a,s,p,!1,u,"662d0b64",null,null);function u(t){for(let e in o)this[e]=o[e]}var st=function(){return f.exports}();export{st as default}; import n from"./FileContent.711a2587.js";import m from"./FilePreview.fa83e27f.js";import{n as l}from"./app.78a291d1.js";import"./openpgp_hi.15f91b1d.js";import"./IFrame.551ecc40.js";import"./jquery.9174f09e.js";import"./@babel.9410f858.js";import"./dayjs.ce1ced10.js";import"./localforage.8ee247f7.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var s=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"single-file"},[i("PageTitle",{attrs:{title:t.pageName}}),t.loadIng>0?i("Loading"):t.fileInfo?[t.isPreview?i("FilePreview",{attrs:{code:t.code,file:t.fileInfo,historyId:t.historyId,headerShow:!t.$isEEUIApp}}):i("FileContent",{attrs:{file:t.fileInfo},model:{value:t.fileShow,callback:function(r){t.fileShow=r},expression:"fileShow"}})]:t._e()],2)},p=[];const a={components:{FilePreview:m,FileContent:n},data(){return{loadIng:0,code:null,fileShow:!0,fileInfo:null}},mounted(){},computed:{historyId(){return this.$route.query?$A.runNum(this.$route.query.history_id):0},isPreview(){return this.windowPortrait||this.code||this.historyId>0||this.fileInfo&&this.fileInfo.permission===0},pageName(){return this.$route.query&&this.$route.query.history_title?this.$route.query.history_title:this.fileInfo?`${this.fileInfo.name} [${this.fileInfo.created_at}]`:""}},watch:{$route:{handler(){this.getInfo()},immediate:!0}},methods:{getInfo(){let{codeOrFileId:t}=this.$route.params,e={id:t};if(/^\d+$/.test(t))this.code=null;else if(t)this.code=t;else return;setTimeout(i=>{this.loadIng++},600),this.$store.dispatch("call",{url:"file/one",data:e}).then(({data:i})=>{this.fileInfo=i}).catch(({msg:i})=>{$A.modalError({content:i,onOk:()=>{window.close()}})}).finally(i=>{this.loadIng--})}}},o={};var f=l(a,s,p,!1,u,"662d0b64",null,null);function u(t){for(let e in o)this[e]=o[e]}var st=function(){return f.exports}();export{st as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{n as m}from"./app.d80882e4.js";import"./jquery.ead8aad7.js";import"./@babel.9410f858.js";import"./dayjs.2db954dd.js";import"./localforage.44d37169.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var e=function(){var t=this,o=t.$createElement,i=t._self._c||o;return i("div")},n=[];const p={data(){return{}},mounted(){if(/^https?:/i.test(window.location.protocol)){let t=null;if(this.$router.mode==="hash"?$A.stringLength(window.location.pathname)>2&&(t=`${window.location.origin}/#${window.location.pathname}${window.location.search}`):this.$router.mode==="history"&&$A.strExists(window.location.href,"/#/")&&(t=window.location.href.replace("/#/","/")),t)throw this.$store.dispatch("userUrl",t).then(o=>{window.location.href=o}),SyntaxError()}},activated(){this.start()},methods:{start(){this.userId>0?this.goForward({name:"manage-dashboard"},!0):this.goForward({name:"login"},!0)}}},r={};var a=m(p,e,n,!1,s,null,null,null);function s(t){for(let o in r)this[o]=r[o]}var rt=function(){return a.exports}();export{rt as default}; import{n as m}from"./app.78a291d1.js";import"./jquery.9174f09e.js";import"./@babel.9410f858.js";import"./dayjs.ce1ced10.js";import"./localforage.8ee247f7.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var e=function(){var t=this,o=t.$createElement,i=t._self._c||o;return i("div")},n=[];const p={data(){return{}},mounted(){if(/^https?:/i.test(window.location.protocol)){let t=null;if(this.$router.mode==="hash"?$A.stringLength(window.location.pathname)>2&&(t=`${window.location.origin}/#${window.location.pathname}${window.location.search}`):this.$router.mode==="history"&&$A.strExists(window.location.href,"/#/")&&(t=window.location.href.replace("/#/","/")),t)throw this.$store.dispatch("userUrl",t).then(o=>{window.location.href=o}),SyntaxError()}},activated(){this.start()},methods:{start(){this.userId>0?this.goForward({name:"manage-dashboard"},!0):this.goForward({name:"login"},!0)}}},r={};var a=m(p,e,n,!1,s,null,null,null);function s(t){for(let o in r)this[o]=r[o]}var rt=function(){return a.exports}();export{rt as default};

1
public/js/build/index.645b2430.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{_ as m}from"./openpgp_hi.15f91b1d.js";import{e as n}from"./index.40a8e116.js";import{n as p}from"./app.d80882e4.js";import"./jquery.ead8aad7.js";import"./@babel.9410f858.js";import"./dayjs.2db954dd.js";import"./localforage.44d37169.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var a=function(){var t=this,o=t.$createElement,i=t._self._c||o;return t.ready?i("VEditor",{attrs:{leftToolbar:t.leftToolbar,rightToolbar:t.rightToolbar,tocNavPositionRight:t.tocNavPositionRight,includeLevel:t.includeLevel},model:{value:t.content,callback:function(e){t.content=e},expression:"content"}}):i("Loading")},s=[];const l={name:"VMEditor",mixins:[n],components:{VEditor:()=>m(()=>import("./editor.31bf1bb8.js"),["js/build/editor.31bf1bb8.js","js/build/editor.2d3fffc9.css","js/build/@kangc.b5fe0a56.js","js/build/@kangc.d8464d83.css","js/build/@babel.9410f858.js","js/build/vue.adba9046.js","js/build/copy-to-clipboard.a53c061d.js","js/build/toggle-selection.d2487283.js","js/build/prismjs.94ec9288.js","js/build/app.d80882e4.js","js/build/app.5b5f242d.css","js/build/jquery.ead8aad7.js","js/build/dayjs.2db954dd.js","js/build/localforage.44d37169.js","js/build/markdown-it.0450edb4.js","js/build/mdurl.ce6c1dd8.js","js/build/uc.micro.8d343c98.js","js/build/entities.48a44fec.js","js/build/linkify-it.c5e8196e.js","js/build/punycode.js.4b3f125a.js","js/build/highlight.js.cbbfb885.js","js/build/markdown-it-link-attributes.e1d5d151.js","js/build/@traptitech.acea8861.js","js/build/vuex.cc7cb26e.js","js/build/openpgp_hi.15f91b1d.js","js/build/axios.37c7f908.js","js/build/mitt.1ea0a2a3.js","js/build/quill-hi.ca2ea0cc.js","js/build/parchment.d5c5924e.js","js/build/quill-delta.385a10bf.js","js/build/fast-diff.f17881f3.js","js/build/lodash.clonedeep.3cc09a31.js","js/build/lodash.isequal.dbdc2157.js","js/build/eventemitter3.78b735ad.js","js/build/lodash-es.76e3a28b.js","js/build/quill-mention-hi.4eeb5a2d.js","js/build/view-design-hi.24419422.js","js/build/html-to-md.f297036e.js","js/build/lodash.8fcd6fd4.js","js/build/vue-router.2d566cd7.js","js/build/vue-clipboard2.fd43a5bc.js","js/build/clipboard.37b37361.js","js/build/vuedraggable.f464b992.js","js/build/sortablejs.3488b922.js","js/build/vue-resize-observer.5af23a43.js","js/build/element-sea.f8a64907.js","js/build/deepmerge.cecf392e.js","js/build/resize-observer-polyfill.5d591c5f.js","js/build/throttle-debounce.7c3948b2.js","js/build/babel-helper-vue-jsx-merge-props.5ed215c3.js","js/build/normalize-wheel.2a034b9f.js","js/build/async-validator.dca2b951.js","js/build/babel-runtime.4773988a.js","js/build/core-js.314b4a1d.js","js/build/codemirror.9d10b9e4.js","js/build/codemirror.9ace6687.css","js/build/index.40a8e116.js","js/build/ImgUpload.444ba02a.js","js/build/chunkedUpload.59d4149a.js","js/build/spark-md5.0443325f.js"])},data(){return{ready:!1,content:""}},async mounted(){await $A.loadScriptS(["js/katex/katex.min.js","js/katex/katex.min.css","js/mermaid.min.js"]),this.ready=!0},watch:{value:{handler(t){t==null&&(t=""),this.content=t},immediate:!0},content(t){this.$emit("input",t)}}},r={};var c=p(l,a,s,!1,_,null,null,null);function _(t){for(let o in r)this[o]=r[o]}var nt=function(){return c.exports}();export{nt as default}; import{_ as m}from"./openpgp_hi.15f91b1d.js";import{e as n}from"./index.40a8e116.js";import{n as p}from"./app.78a291d1.js";import"./jquery.9174f09e.js";import"./@babel.9410f858.js";import"./dayjs.ce1ced10.js";import"./localforage.8ee247f7.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var a=function(){var t=this,o=t.$createElement,i=t._self._c||o;return t.ready?i("VEditor",{attrs:{leftToolbar:t.leftToolbar,rightToolbar:t.rightToolbar,tocNavPositionRight:t.tocNavPositionRight,includeLevel:t.includeLevel},model:{value:t.content,callback:function(e){t.content=e},expression:"content"}}):i("Loading")},s=[];const l={name:"VMEditor",mixins:[n],components:{VEditor:()=>m(()=>import("./editor.173aa79d.js"),["js/build/editor.173aa79d.js","js/build/editor.2d3fffc9.css","js/build/@kangc.b5fe0a56.js","js/build/@kangc.d8464d83.css","js/build/@babel.9410f858.js","js/build/vue.adba9046.js","js/build/copy-to-clipboard.a53c061d.js","js/build/toggle-selection.d2487283.js","js/build/prismjs.94ec9288.js","js/build/app.78a291d1.js","js/build/app.4744fa3b.css","js/build/jquery.9174f09e.js","js/build/dayjs.ce1ced10.js","js/build/localforage.8ee247f7.js","js/build/markdown-it.0450edb4.js","js/build/mdurl.ce6c1dd8.js","js/build/uc.micro.8d343c98.js","js/build/entities.48a44fec.js","js/build/linkify-it.c5e8196e.js","js/build/punycode.js.4b3f125a.js","js/build/highlight.js.cbbfb885.js","js/build/markdown-it-link-attributes.e1d5d151.js","js/build/@traptitech.acea8861.js","js/build/vuex.cc7cb26e.js","js/build/openpgp_hi.15f91b1d.js","js/build/axios.37c7f908.js","js/build/mitt.1ea0a2a3.js","js/build/quill-hi.ca2ea0cc.js","js/build/parchment.d5c5924e.js","js/build/quill-delta.385a10bf.js","js/build/fast-diff.f17881f3.js","js/build/lodash.clonedeep.3cc09a31.js","js/build/lodash.isequal.dbdc2157.js","js/build/eventemitter3.78b735ad.js","js/build/lodash-es.76e3a28b.js","js/build/quill-mention-hi.4eeb5a2d.js","js/build/view-design-hi.24419422.js","js/build/html-to-md.f297036e.js","js/build/lodash.8fcd6fd4.js","js/build/vue-router.2d566cd7.js","js/build/vue-clipboard2.fd43a5bc.js","js/build/clipboard.37b37361.js","js/build/vuedraggable.f464b992.js","js/build/sortablejs.3488b922.js","js/build/vue-resize-observer.5af23a43.js","js/build/element-sea.f8a64907.js","js/build/deepmerge.cecf392e.js","js/build/resize-observer-polyfill.5d591c5f.js","js/build/throttle-debounce.7c3948b2.js","js/build/babel-helper-vue-jsx-merge-props.5ed215c3.js","js/build/normalize-wheel.2a034b9f.js","js/build/async-validator.dca2b951.js","js/build/babel-runtime.4773988a.js","js/build/core-js.314b4a1d.js","js/build/codemirror.9d10b9e4.js","js/build/codemirror.9ace6687.css","js/build/index.40a8e116.js","js/build/ImgUpload.d37939e1.js","js/build/chunkedUpload.5d2e8ab1.js","js/build/spark-md5.0443325f.js"])},data(){return{ready:!1,content:""}},async mounted(){await $A.loadScriptS(["js/katex/katex.min.js","js/katex/katex.min.css","js/mermaid.min.js"]),this.ready=!0},watch:{value:{handler(t){t==null&&(t=""),this.content=t},immediate:!0},content(t){this.$emit("input",t)}}},r={};var c=p(l,a,s,!1,_,null,null,null);function _(t){for(let o in r)this[o]=r[o]}var nt=function(){return c.exports}();export{nt as default};

File diff suppressed because one or more lines are too long

1
public/js/build/ionicons.b2db2c58.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{g as e,l as n,q as s,n as p}from"./app.d80882e4.js";import{m as l}from"./vuex.cc7cb26e.js";import"./jquery.ead8aad7.js";import"./@babel.9410f858.js";import"./dayjs.2db954dd.js";import"./localforage.44d37169.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var u=function(){var t=this,a=t.$createElement,r=t._self._c||a;return r("div",{staticClass:"setting-item submit"},[r("Form",t._b({ref:"formData",attrs:{model:t.formData,rules:t.ruleData},nativeOn:{submit:function(o){o.preventDefault()}}},"Form",t.formOptions,!1),[r("FormItem",{attrs:{label:t.$L("\u9009\u62E9\u8BED\u8A00"),prop:"language"}},[r("Select",{attrs:{placeholder:t.$L("\u9009\u9879\u8BED\u8A00")},model:{value:t.formData.language,callback:function(o){t.$set(t.formData,"language",o)},expression:"formData.language"}},t._l(t.languageList,function(o,i){return r("Option",{key:i,attrs:{value:i}},[t._v(t._s(o))])}),1)],1)],1),r("div",{staticClass:"setting-footer"},[r("Button",{attrs:{loading:t.loadIng>0,type:"primary"},on:{click:t.submitForm}},[t._v(t._s(t.$L("\u63D0\u4EA4")))]),r("Button",{attrs:{loading:t.loadIng>0},on:{click:t.resetForm}},[t._v(t._s(t.$L("\u91CD\u7F6E")))])],1)],1)},f=[];const g={data(){return{loadIng:0,languageList:e,formData:{language:""},ruleData:{}}},mounted(){this.initData()},computed:{...l(["formOptions"])},methods:{initData(){this.$set(this.formData,"language",n),this.formData_bak=$A.cloneJSON(this.formData)},submitForm(){this.$refs.formData.validate(t=>{t&&s(this.formData.language)})},resetForm(){this.formData=$A.cloneJSON(this.formData_bak)}}},m={};var c=p(g,u,f,!1,_,null,null,null);function _(t){for(let a in m)this[a]=m[a]}var st=function(){return c.exports}();export{st as default}; import{g as e,l as n,x as s,n as p}from"./app.78a291d1.js";import{m as l}from"./vuex.cc7cb26e.js";import"./jquery.9174f09e.js";import"./@babel.9410f858.js";import"./dayjs.ce1ced10.js";import"./localforage.8ee247f7.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var u=function(){var t=this,a=t.$createElement,r=t._self._c||a;return r("div",{staticClass:"setting-item submit"},[r("Form",t._b({ref:"formData",attrs:{model:t.formData,rules:t.ruleData},nativeOn:{submit:function(o){o.preventDefault()}}},"Form",t.formOptions,!1),[r("FormItem",{attrs:{label:t.$L("\u9009\u62E9\u8BED\u8A00"),prop:"language"}},[r("Select",{attrs:{placeholder:t.$L("\u9009\u9879\u8BED\u8A00")},model:{value:t.formData.language,callback:function(o){t.$set(t.formData,"language",o)},expression:"formData.language"}},t._l(t.languageList,function(o,i){return r("Option",{key:i,attrs:{value:i}},[t._v(t._s(o))])}),1)],1)],1),r("div",{staticClass:"setting-footer"},[r("Button",{attrs:{loading:t.loadIng>0,type:"primary"},on:{click:t.submitForm}},[t._v(t._s(t.$L("\u63D0\u4EA4")))]),r("Button",{attrs:{loading:t.loadIng>0},on:{click:t.resetForm}},[t._v(t._s(t.$L("\u91CD\u7F6E")))])],1)],1)},f=[];const g={data(){return{loadIng:0,languageList:e,formData:{language:""},ruleData:{}}},mounted(){this.initData()},computed:{...l(["formOptions"])},methods:{initData(){this.$set(this.formData,"language",n),this.formData_bak=$A.cloneJSON(this.formData)},submitForm(){this.$refs.formData.validate(t=>{t&&s(this.formData.language)})},resetForm(){this.formData=$A.cloneJSON(this.formData_bak)}}},m={};var c=p(g,u,f,!1,_,null,null,null);function _(t){for(let a in m)this[a]=m[a]}var st=function(){return c.exports}();export{st as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16
public/js/build/manage.2ff13511.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{n as a}from"./app.d80882e4.js";import"./jquery.ead8aad7.js";import"./@babel.9410f858.js";import"./dayjs.2db954dd.js";import"./localforage.44d37169.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var s=function(){var i=this,t=i.$createElement,r=i._self._c||t;return r("div")},u=[];const c={mounted(){const{meetingId:i,sharekey:t}=this.$route.params,{nickname:r,avatar:m,audio:p,video:n,type:o}=this.$route.query;this.$store.dispatch("showMeetingWindow",{type:["direct","join"].includes(o)?o:"join",meetingid:i,meetingSharekey:t,meetingNickname:r,meetingAvatar:m,meetingAudio:p,meetingVideo:n,meetingdisabled:!0})},render(){return null}},e={};var d=a(c,s,u,!1,l,null,null,null);function l(i){for(let t in e)this[t]=e[t]}var pt=function(){return d.exports}();export{pt as default}; import{n as a}from"./app.78a291d1.js";import"./jquery.9174f09e.js";import"./@babel.9410f858.js";import"./dayjs.ce1ced10.js";import"./localforage.8ee247f7.js";import"./markdown-it.0450edb4.js";import"./mdurl.ce6c1dd8.js";import"./uc.micro.8d343c98.js";import"./entities.48a44fec.js";import"./linkify-it.c5e8196e.js";import"./punycode.js.4b3f125a.js";import"./highlight.js.cbbfb885.js";import"./markdown-it-link-attributes.e1d5d151.js";import"./@traptitech.acea8861.js";import"./vue.adba9046.js";import"./vuex.cc7cb26e.js";import"./openpgp_hi.15f91b1d.js";import"./axios.37c7f908.js";import"./mitt.1ea0a2a3.js";import"./quill-hi.ca2ea0cc.js";import"./parchment.d5c5924e.js";import"./quill-delta.385a10bf.js";import"./fast-diff.f17881f3.js";import"./lodash.clonedeep.3cc09a31.js";import"./lodash.isequal.dbdc2157.js";import"./eventemitter3.78b735ad.js";import"./lodash-es.76e3a28b.js";import"./quill-mention-hi.4eeb5a2d.js";import"./view-design-hi.24419422.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var s=function(){var i=this,t=i.$createElement,r=i._self._c||t;return r("div")},u=[];const c={mounted(){const{meetingId:i,sharekey:t}=this.$route.params,{nickname:r,avatar:m,audio:p,video:n,type:o}=this.$route.query;this.$store.dispatch("showMeetingWindow",{type:["direct","join"].includes(o)?o:"join",meetingid:i,meetingSharekey:t,meetingNickname:r,meetingAvatar:m,meetingAudio:p,meetingVideo:n,meetingdisabled:!0})},render(){return null}},e={};var d=a(c,s,u,!1,l,null,null,null);function l(i){for(let t in e)this[t]=e[t]}var pt=function(){return d.exports}();export{pt as default};

File diff suppressed because one or more lines are too long

1
public/js/build/messenger.a6686f1b.js vendored Normal file

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More