mirror of
https://github.com/kuaifan/dootask.git
synced 2026-10-29 22:10:42 +00:00
Compare commits
No commits in common. "pro" and "v1.9.66" have entirely different histories.
407
.github/workflows/app-build.yml
vendored
407
.github/workflows/app-build.yml
vendored
@ -1,407 +0,0 @@
|
||||
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
34
.github/workflows/app-internal.yml
vendored
@ -1,34 +0,0 @@
|
||||
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
|
||||
404
.github/workflows/ios-publish.yml
vendored
404
.github/workflows/ios-publish.yml
vendored
@ -1,7 +1,5 @@
|
||||
name: "iOS Publish"
|
||||
|
||||
# 构建 Expo App 并上传 App Store Connect(之后在 App Store Connect 提交审核)。
|
||||
#
|
||||
# Required GitHub Secrets:
|
||||
#
|
||||
# IOS_CERTIFICATE_BASE64 - Apple distribution certificate (.p12) encoded in base64
|
||||
@ -14,11 +12,6 @@ name: "iOS Publish"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
upload:
|
||||
description: 上传到 App Store Connect(关闭则只构建 IPA artifact)
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@ -28,34 +21,198 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
uses: ./.github/workflows/app-build.yml
|
||||
with:
|
||||
platform: ios
|
||||
ios_export_method: app-store
|
||||
release: true
|
||||
secrets: inherit
|
||||
prepare-assets:
|
||||
name: Prepare iOS Assets
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
version: ${{ steps.get-version.outputs.version }}
|
||||
|
||||
submit:
|
||||
name: Submit to App Store Connect
|
||||
needs: build
|
||||
if: inputs.upload
|
||||
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:
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Install electron dependencies
|
||||
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
|
||||
timeout-minutes: 60
|
||||
environment: build
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: app-ios
|
||||
path: ipa-output
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
- name: Init mobile submodule
|
||||
run: |
|
||||
git submodule init
|
||||
git submodule update --remote "resources/mobile"
|
||||
|
||||
- name: Download prepared assets
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ios-platform
|
||||
path: resources/mobile/platforms/ios/
|
||||
|
||||
- name: Select Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
|
||||
- name: Upload IPA
|
||||
- name: Install CocoaPods
|
||||
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:
|
||||
ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }}
|
||||
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
|
||||
@ -63,12 +220,196 @@ jobs:
|
||||
run: |
|
||||
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
|
||||
echo "$ASC_API_KEY_P8_BASE64" | base64 --decode > ~/private_keys/AuthKey_${ASC_API_KEY_ID}.p8
|
||||
|
||||
IPA_PATH=$(find ipa-output -name "*.ipa" | head -1)
|
||||
# Find and upload IPA
|
||||
IPA_PATH=$(find $RUNNER_TEMP/ipa-output -name "*.ipa" | head -1)
|
||||
if [ -z "$IPA_PATH" ]; then
|
||||
echo "No IPA file found in ipa-output"
|
||||
echo "No IPA file found in $RUNNER_TEMP/ipa-output"
|
||||
exit 1
|
||||
fi
|
||||
echo "Uploading: $IPA_PATH"
|
||||
@ -81,4 +422,11 @@ jobs:
|
||||
|
||||
- name: Clean up
|
||||
if: always()
|
||||
run: rm -rf ~/private_keys
|
||||
run: |
|
||||
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
|
||||
|
||||
138
.github/workflows/publish.yml
vendored
138
.github/workflows/publish.yml
vendored
@ -131,6 +131,8 @@ jobs:
|
||||
include:
|
||||
- platform: "macos-latest"
|
||||
build_type: "mac"
|
||||
- platform: "ubuntu-latest"
|
||||
build_type: "android"
|
||||
- platform: "windows-latest"
|
||||
build_type: "windows"
|
||||
|
||||
@ -145,6 +147,73 @@ jobs:
|
||||
with:
|
||||
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 构建步骤
|
||||
- name: (Mac) Build Client
|
||||
if: matrix.build_type == 'mac'
|
||||
@ -178,75 +247,8 @@ jobs:
|
||||
run: |
|
||||
./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:
|
||||
needs: [ check-version, create-release, pack-vendor, build-client, upload-android ]
|
||||
needs: [ check-version, create-release, pack-vendor, build-client ]
|
||||
if: needs.check-version.outputs.should_release == 'true' && github.ref == 'refs/heads/pro'
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -66,7 +66,7 @@ jobs:
|
||||
- name: Run PHPUnit in DooTask PHP image
|
||||
run: |
|
||||
docker run --rm --network host -v "$GITHUB_WORKSPACE":/var/www -w /var/www \
|
||||
kuaifan/php:swoole-8.4.27 sh -c '
|
||||
kuaifan/php:swoole-8.4 sh -c '
|
||||
composer install --prefer-dist --no-interaction &&
|
||||
cp .env.example .env &&
|
||||
sed -i "s/^DB_HOST=.*/DB_HOST=127.0.0.1/" .env &&
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -1,6 +1,6 @@
|
||||
[submodule "resources/drawio"]
|
||||
path = resources/drawio
|
||||
url = https://github.com/jgraph/drawio.git
|
||||
[submodule "resources/app"]
|
||||
path = resources/app
|
||||
url = https://github.com/dootask/app.git
|
||||
[submodule "resources/mobile"]
|
||||
path = resources/mobile
|
||||
url = https://github.com/kuaifan/dootask-app.git
|
||||
|
||||
67
CHANGELOG.md
67
CHANGELOG.md
@ -2,73 +2,6 @@
|
||||
|
||||
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]
|
||||
|
||||
### Features
|
||||
|
||||
@ -22,17 +22,10 @@ npm run build # 编译前端
|
||||
- 如果想手动生成客户端执行 `./cmd electron` 根据提示选择操作。
|
||||
|
||||
|
||||
## App(Expo,`resources/app` 子模块)
|
||||
## 编译 App
|
||||
|
||||
- **Android**:推送到 `pro` 时随 Publish 工作流自动构建(`.github/workflows/app-build.yml`),以正式签名出包,上传官网下载(R2)并附到 GitHub Release;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/`)。
|
||||
```shell
|
||||
./cmd appbuild publish # 编译生成App需要的资源
|
||||
```
|
||||
|
||||
| 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 |
|
||||
编译完后进入 `resources/mobile` EEUI框架目录内打包 Android 或 iOS 应用(Android 以实现 GitHub Actions 自动发布)
|
||||
|
||||
@ -430,7 +430,6 @@ class AssistantController extends AbstractController
|
||||
$sessions = AiAssistantSession::where('userid', $user->userid)
|
||||
->where('session_key', $sessionKey)
|
||||
->orderByDesc('updated_at')
|
||||
->limit(20) // 与前端 maxSessionsPerKey 保持一致,避免全量返回拖慢打开
|
||||
->get();
|
||||
|
||||
$list = [];
|
||||
@ -513,19 +512,6 @@ class AssistantController extends AbstractController
|
||||
], $session?->id);
|
||||
$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
|
||||
$urls = [];
|
||||
foreach ($newImageUrls as $imageId => $path) {
|
||||
|
||||
@ -1,82 +0,0 @@
|
||||
<?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('保存成功');
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,6 @@ use Response;
|
||||
use Carbon\Carbon;
|
||||
use App\Module\Doo;
|
||||
use App\Models\User;
|
||||
use App\Module\AppPush\AppPush;
|
||||
use App\Module\Base;
|
||||
use App\Module\OnlineLicense;
|
||||
use App\Module\SystemUpdate;
|
||||
@ -142,45 +141,39 @@ class SystemController extends AbstractController
|
||||
//
|
||||
if ($type == 'all' || $type == 'save') {
|
||||
User::auth('admin');
|
||||
$setting['reg_invite'] = ($setting['reg_invite'] ?? '') ?: Base::generatePassword();
|
||||
$setting['reg_invite'] = $setting['reg_invite'] ?: Base::generatePassword();
|
||||
} else {
|
||||
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['login_logo'] = Base::fillUrl($setting['login_logo'] ?? '');
|
||||
$setting['archived_day'] = floatval($setting['archived_day'] ?? 0) ?: 7;
|
||||
$setting['reg'] = $setting['reg'] ?: 'open';
|
||||
$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['app_ai_hidden'] = config('dootask.app_ai_hidden');
|
||||
$setting['server_timezone'] = config('app.timezone');
|
||||
@ -567,7 +560,7 @@ class SystemController extends AbstractController
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存设置(参数:['doopush'(system 系统配置、custom 自定义、close 关闭), 'doopush_app_id', 'doopush_app_secret', 'doopush_base_url'])
|
||||
* - save: 保存设置(参数:['push', 'ios_key', 'ios_secret', 'android_key', 'android_secret'])
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
@ -584,26 +577,21 @@ class SystemController extends AbstractController
|
||||
$all = Request::input();
|
||||
foreach ($all as $key => $value) {
|
||||
if (!in_array($key, [
|
||||
'doopush',
|
||||
'doopush_app_id',
|
||||
'doopush_app_secret',
|
||||
'doopush_base_url',
|
||||
'push',
|
||||
'ios_key',
|
||||
'ios_secret',
|
||||
'android_key',
|
||||
'android_secret',
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
if (!in_array($all['doopush'] ?? '', ['system', 'custom', 'close'], true)) {
|
||||
return Base::retError('推送模式错误');
|
||||
}
|
||||
$setting = Base::setting('appPushSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('appPushSetting');
|
||||
}
|
||||
//
|
||||
$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'] ?? '';
|
||||
$setting['push'] = $setting['push'] ?: 'close';
|
||||
//
|
||||
return Base::retSuccess($type == 'save' ? '保存成功' : 'success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ use App\Models\ProjectTask;
|
||||
use App\Models\ProjectTaskFile;
|
||||
use App\Models\UserBot;
|
||||
use App\Models\WebSocket;
|
||||
use App\Models\UmengAlias;
|
||||
use App\Models\UserDelete;
|
||||
use App\Models\UserDevice;
|
||||
use App\Models\UserTransfer;
|
||||
@ -1513,6 +1514,85 @@ class UsersController extends AbstractController
|
||||
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 【会议】创建会议、加入会议
|
||||
*
|
||||
|
||||
@ -63,8 +63,11 @@ class IndexController extends InvokeController
|
||||
if (file_exists($hotFile)) {
|
||||
$array = Base::json2array(file_get_contents($hotFile));
|
||||
$style = null;
|
||||
// 开发模式:vite 经 nginx 反代在同源的 base 路径下(见 vite.config.js devProxyPlugin)
|
||||
$script = asset_main(trim($array['base'] ?? '/__vite/', '/') . "/resources/assets/js/app.js");
|
||||
$script = preg_replace("/^(\/\/(.*?))(:\d+)?\//i", "$1:" . $array['APP_DEV_PORT'] . "/", asset_main("resources/assets/js/app.js"));
|
||||
$proxyUri = Base::liveEnv('VSCODE_PROXY_URI');
|
||||
if (is_string($proxyUri) && preg_match('/^https?:\/\//i', $proxyUri)) {
|
||||
$script = preg_replace('/^(https?:\/\/|\/\/)[^\/]+/', rtrim($proxyUri, '/'), $script, 1);
|
||||
}
|
||||
} else {
|
||||
$array = Base::json2array(file_get_contents($manifestFile));
|
||||
$style = asset_main($array['resources/assets/js/app.js']['css'][0]);
|
||||
@ -261,6 +264,7 @@ class IndexController extends InvokeController
|
||||
Task::deliver(new DeleteTmpTask('file'));
|
||||
Task::deliver(new DeleteTmpTask('tmp_file', 24));
|
||||
Task::deliver(new DeleteTmpTask('user_device', 24));
|
||||
Task::deliver(new DeleteTmpTask('umeng_log', 24 * 3));
|
||||
// 删除机器人消息
|
||||
Task::deliver(new DeleteBotMsgTask());
|
||||
// 周期任务
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
<?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';
|
||||
}
|
||||
280
app/Models/UmengAlias.php
Normal file
280
app/Models/UmengAlias.php
Normal file
@ -0,0 +1,280 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
32
app/Models/UmengLog.php
Normal file
32
app/Models/UmengLog.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?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 = [];
|
||||
}
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\AppPush\DeviceRegistry;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Lock;
|
||||
@ -307,7 +306,7 @@ class UserDevice extends AbstractModel
|
||||
}
|
||||
if (isset($hash)) {
|
||||
Cache::forget(self::ck($hash));
|
||||
DeviceRegistry::forgetHash($hash);
|
||||
UmengAlias::whereDeviceHash($hash)->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,282 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\ProjectTask;
|
||||
use App\Module\AppPush\AppPush;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class AppPushTask extends AbstractTask
|
||||
@ -15,7 +15,8 @@ class AppPushTask extends AbstractTask
|
||||
|
||||
public function start()
|
||||
{
|
||||
if (!AppPush::enabled()) {
|
||||
$setting = Base::setting('appPushSetting');
|
||||
if ($setting['push'] !== 'open') {
|
||||
return;
|
||||
}
|
||||
ProjectTask::whereNull("complete_at")
|
||||
|
||||
@ -6,6 +6,7 @@ use App\Models\File;
|
||||
use App\Models\TaskWorker;
|
||||
use App\Models\Tmp;
|
||||
use App\Models\UserDevice;
|
||||
use App\Models\UmengLog;
|
||||
use App\Models\WebSocketTmpMsg;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
@ -124,6 +125,17 @@ class DeleteTmpTask extends AbstractTask
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -248,14 +248,14 @@ class ManticoreSyncTask extends AbstractTask
|
||||
private function runIncrementalSync(): void
|
||||
{
|
||||
// 启动各类型的增量同步命令
|
||||
@shell_exec("php /var/www/artisan manticore:sync-files --i > /dev/null 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 > /dev/null 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 > /dev/null 2>&1 &");
|
||||
@shell_exec("php /var/www/artisan manticore:sync-files --i 2>&1 &");
|
||||
@shell_exec("php /var/www/artisan manticore:sync-users --i 2>&1 &");
|
||||
@shell_exec("php /var/www/artisan manticore:sync-projects --i 2>&1 &");
|
||||
@shell_exec("php /var/www/artisan manticore:sync-tasks --i 2>&1 &");
|
||||
@shell_exec("php /var/www/artisan manticore:sync-msgs --i 2>&1 &");
|
||||
|
||||
// 启动失败重试命令
|
||||
@shell_exec("php /var/www/artisan manticore:retry-failures > /dev/null 2>&1 &");
|
||||
@shell_exec("php /var/www/artisan manticore:retry-failures 2>&1 &");
|
||||
}
|
||||
|
||||
public function end()
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
<?php
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\UmengAlias;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use App\Module\AppPush\AppPush;
|
||||
use App\Module\Base;
|
||||
use Cache;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
|
||||
/**
|
||||
* 推送 APP 消息(DooPush)
|
||||
* 推送友盟消息
|
||||
*/
|
||||
class PushAppMsg extends AbstractTask
|
||||
class PushUmengMsg extends AbstractTask
|
||||
{
|
||||
protected $userid = 0;
|
||||
protected $array = [];
|
||||
@ -31,7 +32,8 @@ class PushAppMsg extends AbstractTask
|
||||
if (empty($this->userid) || empty($this->array)) {
|
||||
return;
|
||||
}
|
||||
if (!AppPush::enabled()) {
|
||||
$setting = Base::setting('appPushSetting');
|
||||
if ($setting['push'] !== 'open') {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -75,7 +77,7 @@ class PushAppMsg extends AbstractTask
|
||||
|
||||
// 直接推送
|
||||
if ($directPushUsers) {
|
||||
AppPush::pushToUsers($directPushUsers, $this->array);
|
||||
UmengAlias::pushMsgToUserid($directPushUsers, $this->array);
|
||||
}
|
||||
|
||||
// 创建延迟推送任务
|
||||
@ -93,7 +95,7 @@ class PushAppMsg extends AbstractTask
|
||||
return;
|
||||
}
|
||||
foreach ($this->endPush as $item) {
|
||||
$task = new PushAppMsg($item['userid'], $item['array']);
|
||||
$task = new PushUmengMsg($item['userid'], $item['array']);
|
||||
$task->delay(10);
|
||||
Task::deliver($task);
|
||||
}
|
||||
@ -6,7 +6,6 @@ use App\Models\User;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use App\Module\AppPush\AppPush;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Services\RequestContext;
|
||||
@ -165,7 +164,7 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
]);
|
||||
}
|
||||
// 开始推送消息
|
||||
$appUserid = [];
|
||||
$umengUserid = [];
|
||||
foreach ($array as $item) {
|
||||
$this->endPush[] = [
|
||||
'userid' => $item['userid'],
|
||||
@ -183,25 +182,26 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
]
|
||||
];
|
||||
if ($item['userid'] != $msg->userid && !$item['silence'] && !$this->silence) {
|
||||
$appUserid[] = $item['userid'];
|
||||
$umengUserid[] = $item['userid'];
|
||||
}
|
||||
}
|
||||
// APP 推送(DooPush)
|
||||
if ($appUserid) {
|
||||
if (AppPush::enabled()) {
|
||||
// umeng推送app
|
||||
if ($umengUserid) {
|
||||
$setting = Base::setting('appPushSetting');
|
||||
if ($setting['push'] === 'open') {
|
||||
if ($msg->userid == -1) {
|
||||
// AI 助手虚拟用户没有会员记录,取自定义昵称或默认名称
|
||||
$appTitle = ($msg->msg['nickname'] ?? '') ?: Doo::translate('AI 助手');
|
||||
$umengTitle = ($msg->msg['nickname'] ?? '') ?: Doo::translate('AI 助手');
|
||||
} else {
|
||||
$appTitle = User::userid2nickname($msg->userid);
|
||||
$umengTitle = User::userid2nickname($msg->userid);
|
||||
}
|
||||
$appBody = WebSocketDialogMsg::previewMsg($msg);
|
||||
$umengBody = WebSocketDialogMsg::previewMsg($msg);
|
||||
if ($dialog->type == 'group') {
|
||||
$appBody = $appTitle . ': ' . $appBody;
|
||||
$appTitle = $dialog->getGroupName();
|
||||
$umengBody = $umengTitle . ': ' . $umengBody;
|
||||
$umengTitle = $dialog->getGroupName();
|
||||
}
|
||||
$langs = User::select(['userid', 'lang'])
|
||||
->whereIn('userid', $appUserid)
|
||||
->whereIn('userid', $umengUserid)
|
||||
->whereDisableAt(null)
|
||||
->get()
|
||||
->groupBy('lang')
|
||||
@ -210,9 +210,9 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
});
|
||||
foreach ($langs as $lang => $uids) {
|
||||
Doo::setLanguage($lang);
|
||||
$appMsg = [
|
||||
'title' => $appTitle,
|
||||
'body' => $appBody,
|
||||
$umengMsg = [
|
||||
'title' => $umengTitle,
|
||||
'body' => $umengBody,
|
||||
'description' => "MID:{$msg->id}",
|
||||
'seconds' => 3600,
|
||||
'badge' => 1,
|
||||
@ -221,7 +221,7 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
'msg_id' => $msg->id,
|
||||
]
|
||||
];
|
||||
$this->endArray[] = new PushAppMsg($uids->toArray(), $appMsg);
|
||||
$this->endArray[] = new PushUmengMsg($uids->toArray(), $umengMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
84
cmd
84
cmd
@ -379,6 +379,18 @@ web_build() {
|
||||
if [ ! -d "./node_modules" ]; then
|
||||
npm install
|
||||
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"
|
||||
#
|
||||
if [ "$type" = "prod" ]; then
|
||||
@ -417,44 +429,6 @@ electron_operate() {
|
||||
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() {
|
||||
local container=$1
|
||||
@ -709,9 +683,6 @@ DooTask 管理脚本
|
||||
serve, dev 启动开发模式
|
||||
build, prod 生产环境构建
|
||||
electron 构建桌面应用
|
||||
expo [build] 编译并同步 Expo App 前端
|
||||
expo dev [参数...] 启动 App 开发模式(Metro + 前端),参数透传给 expo start(如 -c)
|
||||
expo npm <参数...> 在 Expo 子模块执行本机 npm
|
||||
|
||||
🔧 服务管理:
|
||||
up [服务名] 启动容器
|
||||
@ -763,9 +734,6 @@ Usage: ./cmd <command> [options]
|
||||
serve, dev Start dev mode
|
||||
build, prod Production build
|
||||
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:
|
||||
up [service] Start containers
|
||||
@ -898,11 +866,8 @@ handle_install() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 生成应用密钥(LaravelS 可能在 composer install 后已先行启动,需重启以加载新密钥)
|
||||
if [[ -z "$(env_get APP_KEY)" ]]; then
|
||||
exec_judge "container_exec php 'php artisan key:generate'" "$(msg '生成密钥失败')"
|
||||
restart_php
|
||||
fi
|
||||
# 生成应用密钥
|
||||
[[ -z "$(env_get APP_KEY)" ]] && exec_judge "container_exec php 'php artisan key:generate'" "$(msg '生成密钥失败')"
|
||||
|
||||
# 设置生产模式
|
||||
switch_debug "false"
|
||||
@ -1231,8 +1196,8 @@ if [[ "$1" == "help" ]] || [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]] || [[ $#
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 非electron、expo和permission命令需要检查Docker环境
|
||||
if [[ "$1" != "electron" ]] && [[ "$1" != "expo" ]] && [[ "$1" != "permission" ]]; then
|
||||
# 非electron和permission命令需要检查Docker环境
|
||||
if [[ "$1" != "electron" ]] && [[ "$1" != "permission" ]]; then
|
||||
check_docker
|
||||
env_init
|
||||
fi
|
||||
@ -1291,21 +1256,32 @@ case "$1" in
|
||||
shift 1
|
||||
web_build prod
|
||||
;;
|
||||
"expo")
|
||||
"appbuild"|"buildapp")
|
||||
shift 1
|
||||
expo_operate "$@"
|
||||
exit $?
|
||||
electron_operate app "$@"
|
||||
;;
|
||||
"electron")
|
||||
shift 1
|
||||
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")
|
||||
shift 1
|
||||
npm "$@"
|
||||
pushd electron || exit
|
||||
npm "$@"
|
||||
popd || exit
|
||||
docker run $TTY_FLAG --rm -v ${WORK_DIR}/resources/mobile:/work -w /work --entrypoint=/bin/bash node:16 -c "npm $@"
|
||||
;;
|
||||
"doc")
|
||||
shift 1
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
"directorytree/ldaprecord-laravel": "^4.0",
|
||||
"firebase/php-jwt": "^7.1",
|
||||
"guzzlehttp/guzzle": "^7.3.0",
|
||||
"hedeqiang/umeng": "^2.1",
|
||||
"laravel/framework": "^13.0",
|
||||
"laravel/tinker": "^3.0",
|
||||
"laravolt/avatar": "^6.5",
|
||||
|
||||
68
composer.lock
generated
68
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3ce4ce992d9f646b9ad6173c4f0d8605",
|
||||
"content-hash": "9563bf2796d1179ba8740789be1ef49b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@ -1481,6 +1481,72 @@
|
||||
],
|
||||
"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",
|
||||
"version": "5.0.1",
|
||||
|
||||
@ -44,13 +44,6 @@ return [
|
||||
// 临时文件自动清理天数(DeleteTmpTask)
|
||||
'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' => [
|
||||
'max_file_bytes' => 1024 * 1024 * 1024,
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
<?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
|
||||
{
|
||||
// 删除表无法恢复
|
||||
}
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
services:
|
||||
php:
|
||||
container_name: "dootask-php-${APP_ID}"
|
||||
image: "kuaifan/php:swoole-8.4.27"
|
||||
image: "kuaifan/php:swoole-8.4"
|
||||
shm_size: 2G
|
||||
ulimits:
|
||||
core:
|
||||
@ -45,8 +45,6 @@ services:
|
||||
- "${APP_SSL_PORT:-0}:443"
|
||||
environment:
|
||||
APP_SCHEME: "${APP_SCHEME:-auto}"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- ./docker/nginx/default.conf:/etc/nginx/templates/default.conf.template
|
||||
- ./:/var/www
|
||||
|
||||
@ -1518,8 +1518,7 @@ session.sid_bits_per_character = 5
|
||||
; Development Value: On
|
||||
; Production Value: On
|
||||
; http://php.net/session.upload-progress.enabled
|
||||
; 关闭以缓解 CVE-2026-109619(项目使用 Laravel session,不依赖此功能)
|
||||
session.upload_progress.enabled = Off
|
||||
;session.upload_progress.enabled = On
|
||||
|
||||
; Cleanup the progress information as soon as all POST data has been read
|
||||
; (i.e. upload completed).
|
||||
|
||||
@ -7,14 +7,14 @@
|
||||
> - 事件总线: `resources/assets/js/store/events.js`(mitt 实例)
|
||||
> - 仅匹配裸 `emitter.emit/on/off(` 调用;`xxx.emitter.emit(`(如 Quill 内部 emitter)不属于本总线,已排除
|
||||
|
||||
共 **31** 个静态可解析事件,**128** 处 `emitter.emit/on/off` 调用。
|
||||
共 **30** 个静态可解析事件,**125** 处 `emitter.emit/on/off` 调用。
|
||||
|
||||
## 事件清单
|
||||
|
||||
### `addMeeting`
|
||||
|
||||
- **emit(10)**
|
||||
- `resources/assets/js/App.vue:469`
|
||||
- `resources/assets/js/App.vue:422`
|
||||
- `resources/assets/js/pages/manage.vue:1278`
|
||||
- `resources/assets/js/pages/manage.vue:1285`
|
||||
- `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/DialogWrapper.vue:2040`
|
||||
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2048`
|
||||
- `resources/assets/js/pages/manage/messenger.vue:1408`
|
||||
- `resources/assets/js/pages/manage/messenger.vue:1227`
|
||||
- **on(1)**
|
||||
- `resources/assets/js/pages/manage/components/MeetingManager/index.vue:187`
|
||||
- **off(1)**
|
||||
@ -66,30 +66,21 @@
|
||||
- **off(1)**
|
||||
- `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`
|
||||
|
||||
- **emit(1)**
|
||||
- `resources/assets/js/components/Mobile/Tabbar.vue:188`
|
||||
- **on(1)**
|
||||
- `resources/assets/js/pages/manage/messenger.vue:370`
|
||||
- `resources/assets/js/pages/manage/messenger.vue:344`
|
||||
- **off(1)**
|
||||
- `resources/assets/js/pages/manage/messenger.vue:374`
|
||||
- `resources/assets/js/pages/manage/messenger.vue:348`
|
||||
|
||||
### `createGroup`
|
||||
|
||||
- **emit(3)**
|
||||
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2894`
|
||||
- `resources/assets/js/pages/manage/components/UserDetail.vue:294`
|
||||
- `resources/assets/js/pages/manage/messenger.vue:1413`
|
||||
- `resources/assets/js/pages/manage/messenger.vue:1232`
|
||||
- **on(1)**
|
||||
- `resources/assets/js/pages/manage.vue:601`
|
||||
- **off(1)**
|
||||
@ -130,7 +121,7 @@
|
||||
### `observeMicroApp:open`
|
||||
|
||||
- **emit(1)**
|
||||
- `resources/assets/js/store/actions.js:5482`
|
||||
- `resources/assets/js/store/actions.js:5479`
|
||||
- **on(1)**
|
||||
- `resources/assets/js/components/MicroApps/index.vue:202`
|
||||
- **off(1)**
|
||||
@ -208,7 +199,7 @@
|
||||
### `openProjectInvite`
|
||||
|
||||
- **emit(1)**
|
||||
- `resources/assets/js/App.vue:481`
|
||||
- `resources/assets/js/App.vue:434`
|
||||
- **on(1)**
|
||||
- `resources/assets/js/pages/manage/components/ProjectInvite.vue:83`
|
||||
- **off(1)**
|
||||
@ -247,7 +238,7 @@
|
||||
- `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:4546`
|
||||
- `resources/assets/js/pages/manage/messenger.vue:1418`
|
||||
- `resources/assets/js/pages/manage/messenger.vue:1237`
|
||||
- **on(1)**
|
||||
- `resources/assets/js/pages/manage/components/UserDetail.vue:166`
|
||||
- **off(1)**
|
||||
@ -285,7 +276,7 @@
|
||||
|
||||
- **emit(2)**
|
||||
- `resources/assets/js/pages/manage.vue:1153`
|
||||
- `resources/assets/js/pages/manage/setting/index.vue:200`
|
||||
- `resources/assets/js/pages/manage/setting/index.vue:198`
|
||||
- **on(1)**
|
||||
- `resources/assets/js/components/RightBottom.vue:65`
|
||||
- **off(1)**
|
||||
@ -332,7 +323,7 @@
|
||||
|
||||
## 统计
|
||||
|
||||
- 事件总数(静态可解析): **31**
|
||||
- 事件总数(静态可解析): **30**
|
||||
- 只 emit 无 on(疑似死事件): **0**
|
||||
- 只 on 无 emit(无人发射): **1** — `aiAssistantFloatButtonVisibilityChanged`
|
||||
- 动态事件名调用: **1**
|
||||
|
||||
167
electron/build.js
vendored
167
electron/build.js
vendored
@ -26,6 +26,7 @@ const architectures = ["arm64", "x64"];
|
||||
let buildChecked = false,
|
||||
updaterChecked = false;
|
||||
|
||||
const shellQuote = (value) => `'${String(value).replace(/'/g, `'\\''`)}'`;
|
||||
const elapsedSeconds = (startTime) => `${((Date.now() - startTime) / 1000).toFixed(1)}s`;
|
||||
|
||||
/**
|
||||
@ -335,12 +336,8 @@ async function uploadDraftFile(client, localFile, version) {
|
||||
* @param data
|
||||
*/
|
||||
async function startBuild(data) {
|
||||
if ((BUILD_FRONTEND === 'build' || data.id === 'app-expo') && !buildChecked) {
|
||||
if (BUILD_FRONTEND === 'build' && !buildChecked) {
|
||||
buildChecked = true
|
||||
// Expo 每次重新编译,避免把上次桌面端或旧版本的文件带进内测包。
|
||||
if (data.id === 'app-expo') {
|
||||
fse.emptyDirSync(electronDir);
|
||||
}
|
||||
fs.mkdirSync(electronDir, { recursive: true });
|
||||
fse.copySync(path.resolve(__dirname, "index.html"), path.resolve(electronDir, "index.html"))
|
||||
child_process.execSync("npx vite build -- fromcmd electronBuild", {stdio: "inherit"});
|
||||
@ -358,7 +355,7 @@ async function startBuild(data) {
|
||||
apiUrl: utils.formatUrl(data.url) + "api/",
|
||||
}
|
||||
// information
|
||||
if (data.id === 'app-expo') {
|
||||
if (data.id === 'app') {
|
||||
console.log("\n=== 编译信息 ===");
|
||||
console.log("名称:", data.name);
|
||||
console.log("版本:", config.version + ` (${config.codeVerson})`);
|
||||
@ -388,16 +385,11 @@ async function startBuild(data) {
|
||||
// config.js
|
||||
fs.writeFileSync(electronDir + "/config.js", "window.systemInfo = " + JSON.stringify(systemInfo), 'utf8');
|
||||
fs.writeFileSync(electronDir + "/dark", '', 'utf8');
|
||||
if (data.id !== 'app-expo') {
|
||||
fs.writeFileSync(nativeCachePath, utils.formatUrl(data.url));
|
||||
fs.writeFileSync(devloadCachePath, "", 'utf8');
|
||||
}
|
||||
fs.writeFileSync(nativeCachePath, utils.formatUrl(data.url));
|
||||
fs.writeFileSync(devloadCachePath, "", 'utf8');
|
||||
// index.html
|
||||
let manifestFile = path.resolve(electronDir, "manifest.json");
|
||||
if (!fs.existsSync(manifestFile)) {
|
||||
if (data.id === 'app-expo') {
|
||||
throw new Error("manifest.json 未找到,Expo 前端构建失败");
|
||||
}
|
||||
console.error("manifest.json 未找到");
|
||||
return;
|
||||
}
|
||||
@ -409,16 +401,49 @@ async function startBuild(data) {
|
||||
indexString = indexString.replace("<!--script-->", `<script type="module" src="./${manifestContent['resources/assets/js/app.js']['file']}"></script>`);
|
||||
fs.writeFileSync(indexFile, indexString, 'utf8');
|
||||
//
|
||||
if (data.id === 'app-expo') {
|
||||
const appDir = path.resolve(__dirname, "../resources/app");
|
||||
const webDir = path.join(appDir, "assets/web");
|
||||
fse.emptyDirSync(webDir);
|
||||
fse.copySync(electronDir, webDir);
|
||||
fs.writeFileSync(path.join(appDir, "version.json"), JSON.stringify({
|
||||
version: config.version,
|
||||
codeVerson: config.codeVerson,
|
||||
}, null, 2) + "\n", 'utf8');
|
||||
console.log("Expo 前端资源已同步至 resources/app/assets/web/");
|
||||
if (data.id === 'app') {
|
||||
const eeuiDir = path.resolve(__dirname, "../resources/mobile");
|
||||
const publicDir = path.resolve(__dirname, "../resources/mobile/src/public");
|
||||
const containerName = `dootask-eeui-${Date.now()}-${process.pid}`;
|
||||
fse.removeSync(publicDir)
|
||||
fse.copySync(electronDir, publicDir)
|
||||
if (argv[3] === "publish") {
|
||||
// Android config
|
||||
const gradleFile = path.resolve(eeuiDir, "platforms/android/eeuiApp/local.properties")
|
||||
let gradleResult = fs.existsSync(gradleFile) ? fs.readFileSync(gradleFile, 'utf8') : "";
|
||||
gradleResult = gradleResult.replace(/(versionCode|versionName)\s*=\s*(.+?)(\n|$)/g, '')
|
||||
gradleResult += `versionCode = ${config.codeVerson}\nversionName = ${config.version}\n`
|
||||
fs.writeFileSync(gradleFile, gradleResult, 'utf8')
|
||||
// iOS config
|
||||
const xcconfigFile = path.resolve(eeuiDir, "platforms/ios/eeuiApp/Config/Version.xcconfig")
|
||||
let xcconfigResult = fs.existsSync(xcconfigFile) ? fs.readFileSync(xcconfigFile, 'utf8') : "";
|
||||
xcconfigResult = xcconfigResult.replace(/(VERSION_CODE|VERSION_NAME)\s*=\s*(.+?)(\n|$)/g, '')
|
||||
xcconfigResult += `VERSION_CODE = ${config.codeVerson}\nVERSION_NAME = ${config.version}\n`
|
||||
fs.writeFileSync(xcconfigFile, xcconfigResult, 'utf8')
|
||||
}
|
||||
if (['build', 'publish'].includes(argv[3])) {
|
||||
child_process.execSync(
|
||||
`docker run -d --name ${containerName} -v ${shellQuote(eeuiDir)}:/work -w /work kuaifan/eeui-cli:0.0.1 sleep infinity`,
|
||||
{stdio: "ignore", cwd: "resources/mobile"}
|
||||
);
|
||||
try {
|
||||
if (!fs.existsSync(path.resolve(eeuiDir, "node_modules"))) {
|
||||
child_process.execSync(`docker exec ${containerName} npm install`, {stdio: "inherit", cwd: "resources/mobile"});
|
||||
}
|
||||
child_process.execSync(`docker exec ${containerName} node /work/scripts/patch-eeui-build.js`, {stdio: "inherit", cwd: "resources/mobile"});
|
||||
child_process.execSync(`docker exec ${containerName} eeui build --simple`, {stdio: "inherit", cwd: "resources/mobile"});
|
||||
} finally {
|
||||
child_process.execSync(`docker rm -f ${containerName}`, {stdio: "ignore", cwd: "resources/mobile"});
|
||||
}
|
||||
} else {
|
||||
[
|
||||
path.resolve(publicDir, "../../platforms/ios/eeuiApp/bundlejs/eeui/public"),
|
||||
path.resolve(publicDir, "../../platforms/android/eeuiApp/app/src/main/assets/eeui/public"),
|
||||
].some(dir => {
|
||||
fse.removeSync(dir)
|
||||
fse.copySync(electronDir, dir)
|
||||
})
|
||||
}
|
||||
return;
|
||||
}
|
||||
const output = `dist/${data.id.replace(/\./g, '-')}/${platform}`
|
||||
@ -508,89 +533,35 @@ if (["dev"].includes(argv[2])) {
|
||||
fs.writeFileSync(devloadCachePath, utils.formatUrl("localhost:" + env.parsed.APP_PORT), 'utf8');
|
||||
child_process.spawn("npx", ["vite", "--", "fromcmd", "electronDev"], {stdio: "inherit"});
|
||||
child_process.spawn("npm", ["run", "start-quiet"], {stdio: "inherit", cwd: "electron"});
|
||||
} else if (argv[2] === 'app-expo') {
|
||||
if ((argv[3] && !['build', 'dev'].includes(argv[3])) || (argv[3] !== 'dev' && argv.length > 4)) {
|
||||
console.error("用法: node ./electron/build.js app-expo [build|dev [expo start 参数...]]");
|
||||
process.exit(1);
|
||||
}
|
||||
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({
|
||||
name: 'App',
|
||||
id: 'app-expo',
|
||||
// public 是前端“尚未设置服务器”的占位域名,不是静态资源的 origin。
|
||||
// electronBuild 的 ./ 资源路径在 localhost:22223/ 下加载,路由自动使用 hash。
|
||||
url: 'http://public/',
|
||||
configure: {
|
||||
platform: '',
|
||||
archs: [],
|
||||
publish: false,
|
||||
release: true,
|
||||
notarize: false,
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error(error.message || error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
} else if (["app"].includes(argv[2])) {
|
||||
// 编译前端页面给 App
|
||||
let mobileSrcDir = path.resolve(__dirname, "../resources/mobile");
|
||||
if (!fs.existsSync(mobileSrcDir)) {
|
||||
console.error("resources/mobile 未找到");
|
||||
process.exit()
|
||||
}
|
||||
startBuild({
|
||||
name: 'App',
|
||||
id: 'app',
|
||||
platform: '',
|
||||
url: 'http://public/',
|
||||
configure: {
|
||||
platform: '',
|
||||
archs: [],
|
||||
publish: false,
|
||||
release: true,
|
||||
notarize: false,
|
||||
}
|
||||
})
|
||||
} else if (["android-upload"].includes(argv[2])) {
|
||||
// 上传安卓文件到 R2 draft(GitHub Actions):参数为 APK 所在目录(app-build.yml 产出的 app-android)
|
||||
// 上传安卓文件到 R2 draft(GitHub Actions)
|
||||
(async () => {
|
||||
if (!r2.r2Configured()) {
|
||||
console.error("缺少 R2_* 环境变量(R2_ACCESS_KEY_ID/R2_SECRET_ACCESS_KEY/R2_ENDPOINT/R2_BUCKET)")
|
||||
process.exit(1)
|
||||
}
|
||||
const client = r2.createR2Client()
|
||||
const releaseDir = path.resolve(argv[3] || path.resolve(__dirname, "../resources/app/android/app/build/outputs/apk/release"));
|
||||
const releaseDir = path.resolve(__dirname, "../resources/mobile/platforms/android/eeuiApp/app/build/outputs/apk/release");
|
||||
if (!fs.existsSync(releaseDir)) {
|
||||
console.error("发布文件未找到")
|
||||
process.exit(1)
|
||||
|
||||
2
electron/scripts/patch-mac-keychain.js
vendored
2
electron/scripts/patch-mac-keychain.js
vendored
@ -13,7 +13,7 @@ const replacements = [
|
||||
];
|
||||
|
||||
function patchSource(source, version) {
|
||||
if (!['26.8.1', '26.15.3', '26.18.0'].includes(version)) {
|
||||
if (!['26.8.1', '26.15.3'].includes(version)) {
|
||||
throw new Error(`Unsupported app-builder-lib ${version}; review the macOS keychain workaround before building.`);
|
||||
}
|
||||
const count = (text) => source.split(text).length - 1;
|
||||
|
||||
@ -1092,11 +1092,3 @@ WebDAV 未启用或你没有使用权限
|
||||
已开始更新系统
|
||||
关联任务
|
||||
解除关联任务
|
||||
推送设备ID必须为1至64位数字
|
||||
推送厂商错误
|
||||
推送Token不能为空
|
||||
推送服务注册失败,请稍后再试
|
||||
推送设备未注册,请稍后再试
|
||||
推送服务已被停用
|
||||
推送设备绑定失败,请稍后再试
|
||||
推送模式错误
|
||||
|
||||
@ -410,6 +410,7 @@ SMTP服务器
|
||||
参数为空
|
||||
参数配置
|
||||
参数错误
|
||||
友盟推送
|
||||
发布成功
|
||||
发送图片
|
||||
发送成功
|
||||
@ -2843,17 +2844,3 @@ APP 新版本
|
||||
更新内容
|
||||
按字母显示
|
||||
按部门显示
|
||||
DooPush 推送
|
||||
App ID
|
||||
App Secret
|
||||
服务地址
|
||||
DooTask 新版本 (*) 已发布,建议更新以获得更好的使用体验。
|
||||
已是最新版本
|
||||
检查更新失败,请稍后再试
|
||||
当前服务器版本较低,请等待服务器升级后再更新
|
||||
推送服务
|
||||
系统配置
|
||||
使用官方推送服务,无需配置。
|
||||
使用自己的 DooPush 应用,需搭配自行打包的 App。
|
||||
选择复制
|
||||
原文
|
||||
|
||||
@ -5903,6 +5903,18 @@
|
||||
"id": "Kesalahan parameter",
|
||||
"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": "发布成功",
|
||||
"zh": "",
|
||||
@ -40954,269 +40966,5 @@
|
||||
"fr": "Dissocier la tâche",
|
||||
"id": "Putuskan tautan tugas",
|
||||
"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": "Исходный текст"
|
||||
}
|
||||
]
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "DooTask",
|
||||
"version": "1.10.38",
|
||||
"codeVerson": 247,
|
||||
"version": "1.9.66",
|
||||
"codeVerson": 245,
|
||||
"description": "DooTask is task management system.",
|
||||
"scripts": {
|
||||
"start": "./cmd dev",
|
||||
@ -43,7 +43,6 @@
|
||||
"html-to-md": "^0.8.8",
|
||||
"inquirer": "^8.2.0",
|
||||
"internal-ip": "^6.2.0",
|
||||
"ionicons": "^8.1.0",
|
||||
"jquery": "^3.6.4",
|
||||
"jspdf": "^2.5.1",
|
||||
"less": "^4.1.2",
|
||||
|
||||
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
4
public/js/build/DialogWrapper.2be94133.js
vendored
Normal file
4
public/js/build/DialogWrapper.2be94133.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
public/js/build/DialogWrapper.66e32264.js
vendored
4
public/js/build/DialogWrapper.66e32264.js
vendored
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
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
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
File diff suppressed because one or more lines are too long
1
public/js/build/TaskDetail.d3b1170d.js
vendored
Normal file
1
public/js/build/TaskDetail.d3b1170d.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/build/TaskDetail.d85a25ef.js
vendored
1
public/js/build/TaskDetail.d85a25ef.js
vendored
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
7
public/js/build/app.4744fa3b.css
vendored
File diff suppressed because one or more lines are too long
7
public/js/build/app.5b5f242d.css
vendored
Normal file
7
public/js/build/app.5b5f242d.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
1
public/js/build/apps.1ff47f0a.js
vendored
Normal file
1
public/js/build/apps.1ff47f0a.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
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
1
public/js/build/apps.32d67b51.js
vendored
@ -1 +0,0 @@
|
||||
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
@ -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.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};
|
||||
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};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
1
public/js/build/dialog.7965223c.js
vendored
Normal file
1
public/js/build/dialog.7965223c.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
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
1
public/js/build/dialog.b2b7a24f.js
vendored
@ -1 +0,0 @@
|
||||
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
1
public/js/build/file.02453831.js
vendored
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
1
public/js/build/file.fec97258.js
vendored
Normal file
1
public/js/build/file.fec97258.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
File diff suppressed because one or more lines are too long
1
public/js/build/index.05bfc75e.js
vendored
Normal file
1
public/js/build/index.05bfc75e.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
File diff suppressed because one or more lines are too long
1
public/js/build/index.645b2430.js
vendored
1
public/js/build/index.645b2430.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
1
public/js/build/ionicons.b2db2c58.js
vendored
1
public/js/build/ionicons.b2db2c58.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
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.0e0f2de1.js
vendored
Normal file
16
public/js/build/manage.0e0f2de1.js
vendored
Normal file
File diff suppressed because one or more lines are too long
16
public/js/build/manage.2ff13511.js
vendored
16
public/js/build/manage.2ff13511.js
vendored
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
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};
|
||||
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};
|
||||
1
public/js/build/messenger.23b72d1d.js
vendored
Normal file
1
public/js/build/messenger.23b72d1d.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/build/messenger.a6686f1b.js
vendored
1
public/js/build/messenger.a6686f1b.js
vendored
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
Loading…
x
Reference in New Issue
Block a user