mirror of
https://github.com/kuaifan/dootask.git
synced 2026-06-11 18:02:22 +00:00
Merge branch 'kuaifan:pro' into pro
This commit is contained in:
commit
635f6e5d5a
251
.github/workflows/ios-publish.yml
vendored
251
.github/workflows/ios-publish.yml
vendored
@ -5,6 +5,7 @@ name: "iOS Publish"
|
||||
# IOS_CERTIFICATE_BASE64 - Apple distribution certificate (.p12) encoded in base64
|
||||
# IOS_CERTIFICATE_PASSWORD - Password for the .p12 certificate
|
||||
# IOS_PROVISION_PROFILE_BASE64 - App Store provisioning profile (.mobileprovision) encoded in base64
|
||||
# IOS_SHARE_EXTENSION_PROVISION_PROFILE_BASE64 - Share extension App Store provisioning profile (.mobileprovision) encoded in base64
|
||||
# ASC_API_KEY_P8_BASE64 - App Store Connect API key (.p8) encoded in base64
|
||||
# ASC_API_KEY_ID - App Store Connect API Key ID
|
||||
# ASC_ISSUER_ID - App Store Connect Issuer ID
|
||||
@ -12,10 +13,18 @@ name: "iOS Publish"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ios-publish-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
prepare-assets:
|
||||
name: Prepare iOS Assets
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
version: ${{ steps.get-version.outputs.version }}
|
||||
|
||||
@ -60,7 +69,8 @@ jobs:
|
||||
build-ios:
|
||||
name: Build & Submit iOS
|
||||
needs: prepare-assets
|
||||
runs-on: macos-15
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 60
|
||||
environment: build
|
||||
|
||||
steps:
|
||||
@ -118,37 +128,257 @@ jobs:
|
||||
- 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: |
|
||||
PROFILE_PATH=$RUNNER_TEMP/profile.mobileprovision
|
||||
echo "$IOS_PROVISION_PROFILE_BASE64" | base64 --decode > "$PROFILE_PATH"
|
||||
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 "$PROFILE_PATH" ~/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 }}
|
||||
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'
|
||||
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'
|
||||
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>
|
||||
@ -170,12 +400,18 @@ jobs:
|
||||
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
|
||||
|
||||
# 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 $RUNNER_TEMP/ipa-output"
|
||||
exit 1
|
||||
fi
|
||||
echo "Uploading: $IPA_PATH"
|
||||
|
||||
xcrun altool --upload-app \
|
||||
@ -189,5 +425,8 @@ jobs:
|
||||
run: |
|
||||
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db 2>/dev/null || true
|
||||
rm -f $RUNNER_TEMP/certificate.p12
|
||||
rm -f $RUNNER_TEMP/profile.mobileprovision
|
||||
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
|
||||
|
||||
28
.github/workflows/publish.yml
vendored
28
.github/workflows/publish.yml
vendored
@ -180,8 +180,11 @@ jobs:
|
||||
- name: (Android) Upload File
|
||||
if: matrix.build_type == 'android'
|
||||
env:
|
||||
UPLOAD_TOKEN: ${{ secrets.UPLOAD_TOKEN }}
|
||||
UPLOAD_URL: ${{ secrets.UPLOAD_URL }}
|
||||
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
|
||||
|
||||
@ -219,8 +222,11 @@ jobs:
|
||||
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
UPLOAD_TOKEN: ${{ secrets.UPLOAD_TOKEN }}
|
||||
UPLOAD_URL: ${{ secrets.UPLOAD_URL }}
|
||||
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 }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
@ -230,8 +236,11 @@ jobs:
|
||||
- name: (Windows) Build Client
|
||||
if: matrix.build_type == 'windows'
|
||||
env:
|
||||
UPLOAD_TOKEN: ${{ secrets.UPLOAD_TOKEN }}
|
||||
UPLOAD_URL: ${{ secrets.UPLOAD_URL }}
|
||||
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 }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
shell: bash
|
||||
@ -264,8 +273,11 @@ jobs:
|
||||
|
||||
- name: Upload Changelog & Publish to Website
|
||||
env:
|
||||
UPLOAD_TOKEN: ${{ secrets.UPLOAD_TOKEN }}
|
||||
UPLOAD_URL: ${{ secrets.UPLOAD_URL }}
|
||||
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
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -61,3 +61,6 @@ laravels.pid
|
||||
|
||||
# Documentation
|
||||
README_LOCAL.md
|
||||
|
||||
# playwright
|
||||
.playwright-mcp/
|
||||
|
||||
20
CHANGELOG.md
20
CHANGELOG.md
@ -2,6 +2,26 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.7.55]
|
||||
|
||||
### Features
|
||||
|
||||
- 新增部门负责人只读视角,可查看部门成员的项目和任务,并按可见性设置控制展示范围。
|
||||
- 群组、项目和部门支持主负责人 + 副负责人,协作管理更灵活。
|
||||
- 新增共享任务模板,支持跨项目使用、搜索和使用统计,复用常用任务更方便。
|
||||
- 管理页侧边栏支持拖拽调整宽度,使用不同屏幕时更顺手。
|
||||
- 优化任务添加界面,模板浏览和加载提示更清晰。
|
||||
- 项目归档设置选择系统默认规则时,会显示对应提示,减少误操作。
|
||||
- 聊天消息中的表格显示更稳定,单元格内容不再随意换行。
|
||||
- 支持按需调整翻译使用的模型,便于适配不同使用场景。
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 修复权限变更过程中可能出现的可见性或访问异常。
|
||||
- 修复 AI 自动分析开关状态判断不准确的问题。
|
||||
- 修复用户详情页在部分情况下出现横向滚动的问题。
|
||||
- 优化应用发布流程,提升发布稳定性。
|
||||
|
||||
## [1.7.29]
|
||||
|
||||
### Features
|
||||
|
||||
15
CLAUDE.md
15
CLAUDE.md
@ -6,10 +6,17 @@ Laravel 8 (LaravelS/Swoole) + Vue 2 (Vite) + Electron。开源任务/项目管
|
||||
|
||||
所有命令通过 `./cmd` 脚本执行(不要直接运行 `php artisan` 等):
|
||||
|
||||
- `./cmd dev` — 前端开发服务器(Node.js 20+)
|
||||
- `./cmd prod` — 构建前端生产版本
|
||||
- `./cmd artisan ...` / `./cmd composer ...` / `./cmd php ...` — PHP 相关命令
|
||||
|
||||
### AI 不要主动执行的命令
|
||||
|
||||
以下命令仅由用户人工触发,AI 不要主动跑——包括"任务完成后 sanity check"、"看下能不能编译"等场景:
|
||||
|
||||
- `./cmd dev` — 用户已自行运行 dev server,改完会自己 reload;AI 再跑会争抢进程
|
||||
- `./cmd prod` / `./cmd build` — 发版才用,走 `/release` 流程
|
||||
|
||||
前端代码改动只做 Edit/Write,不要为了"验证"启动 dev server。用户明确说"跑一下 / 出包"时除外。
|
||||
|
||||
## Gotchas
|
||||
|
||||
### LaravelS/Swoole
|
||||
@ -41,6 +48,10 @@ Laravel 8 (LaravelS/Swoole) + Vue 2 (Vite) + Electron。开源任务/项目管
|
||||
- 新增用户可见文本须追加原文(简体中文)到:前端 `language/original-web.txt`,后端 `language/original-api.txt`(去重)
|
||||
- 前端翻译用 `$L("文本")`,动态值用 `(*)` 占位:`$L('共(*)条', n)`——禁止拼接翻译
|
||||
|
||||
## Playwright 测试
|
||||
|
||||
- Playwright 测试结果放在 `tests/playwright-results/`,包含测试环境、测试用例、结果截图等信息
|
||||
|
||||
## 交互规范
|
||||
|
||||
- **提问时附带建议**:当需要向用户提问或请求澄清时,应同时提供具体的建议选项或推荐方案,帮助用户快速决策,而非仅抛出开放式问题
|
||||
|
||||
@ -1755,8 +1755,10 @@ class DialogController extends AbstractController
|
||||
}
|
||||
// 任务可见性校验(与 task__one 一致)
|
||||
if ($task->visibility != 1) {
|
||||
$project_userid = ProjectUser::whereProjectId($task->project_id)->whereOwner(1)->value('userid');
|
||||
if ($user->userid != $project_userid) {
|
||||
$projectOwnerids = ProjectUser::whereProjectId($task->project_id)
|
||||
->whereIn('owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY])
|
||||
->pluck('userid')->map(fn($v) => (int)$v)->toArray();
|
||||
if (!in_array($user->userid, $projectOwnerids)) {
|
||||
$visibleUserids = array_merge(
|
||||
ProjectTaskUser::whereTaskId($task_id)->pluck('userid')->toArray(),
|
||||
ProjectTaskUser::whereTaskPid($task_id)->pluck('userid')->toArray(),
|
||||
@ -2832,7 +2834,10 @@ class DialogController extends AbstractController
|
||||
return Base::retError('对话不存在或已被删除', ['dialog_id' => $dialog_id], -4003);
|
||||
}
|
||||
} else {
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id, true);
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id);
|
||||
if (!$dialog->isOwner(User::userid())) {
|
||||
throw new \App\Exceptions\ApiException('仅群主或群管理员可操作');
|
||||
}
|
||||
}
|
||||
//
|
||||
$data = ['id' => $dialog->id];
|
||||
@ -2891,7 +2896,11 @@ class DialogController extends AbstractController
|
||||
return Base::retError('请选择群成员');
|
||||
}
|
||||
//
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id, "auto");
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id);
|
||||
// 有群主时,仅群主/群管理员可邀请;无群主时,任意成员可邀请
|
||||
if ($dialog->owner_id > 0 && !$dialog->isOwner($user->userid)) {
|
||||
throw new \App\Exceptions\ApiException('仅限群主或群管理员操作');
|
||||
}
|
||||
//
|
||||
$dialog->checkGroup();
|
||||
$dialog->joinGroup($userids, $user->userid);
|
||||
@ -2981,17 +2990,107 @@ class DialogController extends AbstractController
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id, $check_owner);
|
||||
//
|
||||
$dialog->checkGroup($check_owner ? 'user' : null);
|
||||
$oldOwnerId = (int)$dialog->owner_id;
|
||||
$dialog->owner_id = $userid;
|
||||
if ($dialog->save()) {
|
||||
$dialog->joinGroup($userid, 0);
|
||||
// 同步 role:原主 role=0、新主 role=1(覆盖即可)
|
||||
if ($oldOwnerId > 0 && $oldOwnerId !== (int)$userid) {
|
||||
WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', $oldOwnerId)
|
||||
->update(['role' => 0]);
|
||||
}
|
||||
WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', $userid)
|
||||
->update(['role' => 1]);
|
||||
$dialog->pushMsg("groupUpdate", [
|
||||
'id' => $dialog->id,
|
||||
'owner_id' => $dialog->owner_id,
|
||||
'deputy_ids' => $dialog->deputy_ids,
|
||||
]);
|
||||
}
|
||||
return Base::retSuccess('转让成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 任命群管理员(仅群主可操作)
|
||||
*
|
||||
* @apiParam {Number} dialog_id 群对话ID
|
||||
* @apiParam {Number} userid 要任命的群成员 userid
|
||||
*/
|
||||
public function group__adddeputy()
|
||||
{
|
||||
$user = User::auth();
|
||||
$dialog_id = intval(Request::input('dialog_id'));
|
||||
$userid = intval(Request::input('userid'));
|
||||
|
||||
if ($userid <= 0) {
|
||||
return Base::retError('请选择有效的成员');
|
||||
}
|
||||
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id, true); // checkOwner=true:仅群主
|
||||
$dialog->checkGroup('user'); // 仅普通群
|
||||
|
||||
$member = WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', $userid)
|
||||
->first();
|
||||
if (empty($member)) {
|
||||
return Base::retError('该用户不是群成员');
|
||||
}
|
||||
|
||||
if ((int)$member->role === 1) {
|
||||
return Base::retError('不能将群主任命为群管理员');
|
||||
}
|
||||
if ((int)$member->role !== 2) {
|
||||
$member->role = 2;
|
||||
$member->save();
|
||||
$dialog->pushMsg('groupUpdate', [
|
||||
'id' => $dialog->id,
|
||||
'deputy_ids' => $dialog->fresh()->deputy_ids,
|
||||
]);
|
||||
}
|
||||
|
||||
return Base::retSuccess('任命成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 罢免群管理员(仅群主可操作)
|
||||
*
|
||||
* @apiParam {Number} dialog_id 群对话ID
|
||||
* @apiParam {Number} userid 要罢免的群管理员 userid
|
||||
*/
|
||||
public function group__deldeputy()
|
||||
{
|
||||
$user = User::auth();
|
||||
$dialog_id = intval(Request::input('dialog_id'));
|
||||
$userid = intval(Request::input('userid'));
|
||||
|
||||
if ($userid <= 0) {
|
||||
return Base::retError('请选择有效的成员');
|
||||
}
|
||||
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id, true);
|
||||
$dialog->checkGroup('user');
|
||||
|
||||
$member = WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', $userid)
|
||||
->first();
|
||||
if (empty($member)) {
|
||||
return Base::retSuccess('罢免成功'); // 幂等:本来就不是成员
|
||||
}
|
||||
|
||||
if ((int)$member->role === 2) {
|
||||
$member->role = 0;
|
||||
$member->save();
|
||||
$dialog->pushMsg('groupUpdate', [
|
||||
'id' => $dialog->id,
|
||||
'deputy_ids' => $dialog->fresh()->deputy_ids,
|
||||
]);
|
||||
}
|
||||
|
||||
return Base::retSuccess('罢免成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/group/disband 解散群组
|
||||
*
|
||||
|
||||
@ -46,6 +46,7 @@ use App\Models\ProjectTaskTemplate;
|
||||
use App\Models\ProjectTag;
|
||||
use App\Models\ProjectTaskRelation;
|
||||
use App\Models\ProjectTaskAiEvent;
|
||||
use App\Models\UserDepartment;
|
||||
use App\Module\AiTaskSuggestion;
|
||||
use App\Observers\ProjectTaskObserver;
|
||||
|
||||
@ -128,6 +129,7 @@ class ProjectController extends AbstractController
|
||||
public function lists()
|
||||
{
|
||||
$user = User::auth();
|
||||
$departmentView = UserDepartment::ownerViewContext($user);
|
||||
//
|
||||
$all = Request::input('all');
|
||||
$type = Request::input('type', 'all');
|
||||
@ -141,6 +143,9 @@ class ProjectController extends AbstractController
|
||||
if ($all) {
|
||||
$user->identity('admin');
|
||||
$builder = Project::allData();
|
||||
} elseif ($departmentView['enabled']) {
|
||||
$projectIds = array_values(array_unique(array_merge($departmentView['own_project_ids'], $departmentView['project_ids'])));
|
||||
$builder = Project::allData()->whereIn('projects.id', $projectIds);
|
||||
} else {
|
||||
$builder = Project::authData();
|
||||
}
|
||||
@ -180,8 +185,9 @@ class ProjectController extends AbstractController
|
||||
->orderBy('project_users.sort')
|
||||
->orderByDesc('projects.id')
|
||||
->paginate(Base::getPaginate(100, 50));
|
||||
$list->transform(function (Project $project) use ($getstatistics, $getuserid, $user) {
|
||||
$list->transform(function (Project $project) use ($getstatistics, $getuserid, $user, $departmentView) {
|
||||
$array = $project->toArray();
|
||||
$array = UserDepartment::appendDepartmentReadonlyProject($array, $departmentView);
|
||||
if ($getuserid == 'yes') {
|
||||
$array['userid_list'] = ProjectUser::whereProjectId($project->id)->pluck('userid')->toArray();
|
||||
}
|
||||
@ -250,13 +256,15 @@ class ProjectController extends AbstractController
|
||||
public function one()
|
||||
{
|
||||
$user = User::auth();
|
||||
$departmentView = UserDepartment::ownerViewContext($user, true);
|
||||
//
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
//
|
||||
$project = Project::userProject($project_id);
|
||||
$project = Project::findForDepartmentView($project_id);
|
||||
$data = array_merge($project->toArray(), $project->getTaskStatistics($user->userid), [
|
||||
'project_user' => $project->projectUser,
|
||||
]);
|
||||
$data = UserDepartment::appendDepartmentReadonlyProject($data, $departmentView);
|
||||
//
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
@ -302,6 +310,8 @@ class ProjectController extends AbstractController
|
||||
* @apiParam {String} [archive_method] 归档方式
|
||||
* @apiParam {Number} [archive_days] 自动归档天数
|
||||
* @apiParam {String} [ai_auto_analyze] AI自动分析(open|close)
|
||||
* @apiParam {String} [task_template_share] 共享模板(open|close)
|
||||
* @apiParam {String} [department_owner_view] 部门负责人视角可见(open|close)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -317,6 +327,8 @@ class ProjectController extends AbstractController
|
||||
$archive_method = Request::input('archive_method');
|
||||
$archive_days = intval(Request::input('archive_days'));
|
||||
$ai_auto_analyze = Request::input('ai_auto_analyze');
|
||||
$task_template_share = Request::input('task_template_share');
|
||||
$department_owner_view = Request::input('department_owner_view');
|
||||
if (mb_strlen($name) < 2) {
|
||||
return Base::retError('项目名称不可以少于2个字');
|
||||
} elseif (mb_strlen($name) > 32) {
|
||||
@ -332,7 +344,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
//
|
||||
$project = Project::userProject($project_id, true, true);
|
||||
AbstractModel::transaction(function () use ($archive_days, $archive_method, $ai_auto_analyze, $desc, $name, $project) {
|
||||
AbstractModel::transaction(function () use ($archive_days, $archive_method, $ai_auto_analyze, $task_template_share, $department_owner_view, $desc, $name, $project) {
|
||||
if ($project->name != $name) {
|
||||
$project->addLog("修改项目名称", [
|
||||
'change' => [$project->name, $name]
|
||||
@ -364,6 +376,18 @@ class ProjectController extends AbstractController
|
||||
]);
|
||||
$project->ai_auto_analyze = $ai_auto_analyze;
|
||||
}
|
||||
if (in_array($task_template_share, ['open', 'close']) && $project->task_template_share != $task_template_share) {
|
||||
$project->addLog("修改共享模板", [
|
||||
'change' => [$project->task_template_share, $task_template_share]
|
||||
]);
|
||||
$project->task_template_share = $task_template_share;
|
||||
}
|
||||
if (in_array($department_owner_view, ['open', 'close']) && $project->department_owner_view != $department_owner_view) {
|
||||
$project->addLog("修改负责人视角可见", [
|
||||
'change' => [$project->department_owner_view, $department_owner_view]
|
||||
]);
|
||||
$project->department_owner_view = $department_owner_view;
|
||||
}
|
||||
$project->save();
|
||||
});
|
||||
$project->pushMsg('update');
|
||||
@ -372,15 +396,16 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/user 修改项目成员
|
||||
* @api {post} api/project/user 修改项目成员
|
||||
*
|
||||
* @apiDescription 需要token身份(限:项目负责人)
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName user
|
||||
*
|
||||
* @apiParam {Number} project_id 项目ID
|
||||
* @apiParam {Number} userid 成员ID 或 成员ID组
|
||||
* @apiParam {Number} project_id 项目ID
|
||||
* @apiParam {Number[]} userid 成员userid数组(最终完整列表)
|
||||
* @apiParam {Number[]} [deputy_userid] 项目管理员userid数组(可选,仅负责人有效;必须是 userid 子集)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -393,6 +418,13 @@ class ProjectController extends AbstractController
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
$userid = Request::input('userid');
|
||||
$userid = is_array($userid) ? $userid : [$userid];
|
||||
$userid = array_values(array_unique(array_map('intval', $userid)));
|
||||
//
|
||||
$deputy_userid = Request::input('deputy_userid');
|
||||
if ($deputy_userid !== null) {
|
||||
$deputy_userid = is_array($deputy_userid) ? $deputy_userid : [$deputy_userid];
|
||||
$deputy_userid = array_values(array_unique(array_map('intval', $deputy_userid)));
|
||||
}
|
||||
//
|
||||
if (count($userid) > 100) {
|
||||
return Base::retError('项目人数最多100个');
|
||||
@ -400,7 +432,45 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$project = Project::userProject($project_id, true, true);
|
||||
//
|
||||
$deleteUser = AbstractModel::transaction(function() use ($project, $userid) {
|
||||
// 仅负责人可设置项目管理员;项目管理员/其他角色提交 deputy_userid 一律忽略
|
||||
$isPrimary = (int)$project->owner === ProjectUser::OWNER_PRIMARY;
|
||||
$applyDeputy = $isPrimary && $deputy_userid !== null;
|
||||
//
|
||||
// 业务闭环:项目必须且只能有一个主负责人,最终成员列表必须包含该负责人
|
||||
$primaryOwnerIds = ProjectUser::whereProjectId($project->id)
|
||||
->whereOwner(ProjectUser::OWNER_PRIMARY)
|
||||
->pluck('userid')
|
||||
->map(fn($v) => (int)$v)
|
||||
->toArray();
|
||||
if (count($primaryOwnerIds) !== 1) {
|
||||
return Base::retError('项目负责人数据异常,请先修复项目负责人');
|
||||
}
|
||||
$primaryOwnerId = $primaryOwnerIds[0];
|
||||
if (!in_array($primaryOwnerId, $userid, true)) {
|
||||
return Base::retError('项目成员列表必须包含项目负责人');
|
||||
}
|
||||
// 项目管理员可以管理普通成员,但不能借成员列表移除其他项目管理员
|
||||
if (!$isPrimary) {
|
||||
$currentDeputyIds = ProjectUser::whereProjectId($project->id)
|
||||
->whereOwner(ProjectUser::OWNER_DEPUTY)
|
||||
->pluck('userid')
|
||||
->map(fn($v) => (int)$v)
|
||||
->toArray();
|
||||
if (!empty(array_diff($currentDeputyIds, $userid))) {
|
||||
return Base::retError('项目管理员不能移除项目负责人或项目管理员');
|
||||
}
|
||||
}
|
||||
//
|
||||
if ($applyDeputy) {
|
||||
if (!empty(array_diff($deputy_userid, $userid))) {
|
||||
return Base::retError('项目管理员必须是项目成员');
|
||||
}
|
||||
if (in_array((int)$project->owner_userid, $deputy_userid, true)) {
|
||||
return Base::retError('负责人不能任命为项目管理员');
|
||||
}
|
||||
}
|
||||
//
|
||||
$deleteUser = AbstractModel::transaction(function() use ($project, $userid, $applyDeputy, $deputy_userid) {
|
||||
$array = [];
|
||||
foreach ($userid as $uid) {
|
||||
if ($project->joinProject($uid)) {
|
||||
@ -408,15 +478,37 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
}
|
||||
$deleteRows = ProjectUser::whereProjectId($project->id)->whereNotIn('userid', $array)->get();
|
||||
$deleteUser = $deleteRows->pluck('userid');
|
||||
$deleteUserids = $deleteRows->pluck('userid');
|
||||
foreach ($deleteRows as $row) {
|
||||
$row->exitProject();
|
||||
}
|
||||
//
|
||||
// 项目管理员 diff(仅负责人有效)
|
||||
if ($applyDeputy) {
|
||||
$currentDeputies = ProjectUser::whereProjectId($project->id)
|
||||
->where('owner', ProjectUser::OWNER_DEPUTY)
|
||||
->pluck('userid')->toArray();
|
||||
$toPromote = array_values(array_diff($deputy_userid, $currentDeputies));
|
||||
$toDemote = array_values(array_diff($currentDeputies, $deputy_userid));
|
||||
if (!empty($toPromote)) {
|
||||
ProjectUser::whereProjectId($project->id)
|
||||
->whereIn('userid', $toPromote)
|
||||
->where('owner', ProjectUser::OWNER_MEMBER)
|
||||
->change(['owner' => ProjectUser::OWNER_DEPUTY]);
|
||||
}
|
||||
if (!empty($toDemote)) {
|
||||
ProjectUser::whereProjectId($project->id)
|
||||
->whereIn('userid', $toDemote)
|
||||
->where('owner', ProjectUser::OWNER_DEPUTY)
|
||||
->change(['owner' => ProjectUser::OWNER_MEMBER]);
|
||||
}
|
||||
}
|
||||
//
|
||||
$project->syncDialogUser();
|
||||
$project->addLog("修改项目成员");
|
||||
$project->user_simple = count($array) . "|" . implode(",", array_slice($array, 0, 3));
|
||||
$project->save();
|
||||
return $deleteUser->toArray();
|
||||
return $deleteUserids->toArray();
|
||||
});
|
||||
//
|
||||
$project->pushMsg('delete', null, $deleteUser);
|
||||
@ -574,28 +666,138 @@ class ProjectController extends AbstractController
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
$owner_userid = intval(Request::input('owner_userid'));
|
||||
//
|
||||
$project = Project::userProject($project_id, true, true);
|
||||
$project = Project::userProject($project_id, true, 'primary');
|
||||
//
|
||||
if (!User::whereUserid($owner_userid)->exists()) {
|
||||
return Base::retError('成员不存在');
|
||||
}
|
||||
//
|
||||
AbstractModel::transaction(function() use ($owner_userid, $project) {
|
||||
ProjectUser::whereProjectId($project->id)->change(['owner' => 0]);
|
||||
// 仅清除原负责人 owner=1(项目管理员 owner=2 保留)
|
||||
ProjectUser::whereProjectId($project->id)
|
||||
->whereOwner(ProjectUser::OWNER_PRIMARY)
|
||||
->change(['owner' => 0]);
|
||||
// 设新负责人 owner=1(如新负责人原本是项目管理员,从 2 升为 1)
|
||||
ProjectUser::updateInsert([
|
||||
'project_id' => $project->id,
|
||||
'userid' => $owner_userid,
|
||||
], [
|
||||
'owner' => 1,
|
||||
'owner' => ProjectUser::OWNER_PRIMARY,
|
||||
]);
|
||||
// 同步项目群 owner_id
|
||||
if ($project->dialog_id > 0) {
|
||||
$dialog = WebSocketDialog::find($project->dialog_id);
|
||||
if ($dialog) {
|
||||
$dialog->owner_id = $owner_userid;
|
||||
$dialog->save();
|
||||
}
|
||||
}
|
||||
// 同步成员 + role(syncDialogUser 已根据 owner 设置 role)
|
||||
$project->syncDialogUser();
|
||||
$project->addLog("移交项目给", ['userid' => $owner_userid]);
|
||||
});
|
||||
//
|
||||
$project->pushMsg('detail');
|
||||
// pushMsg 带 deputy_userids,前端可直接更新项目管理员列表无需重拉
|
||||
$project->pushMsg('detail', [
|
||||
'owner_userid' => $project->fresh()->owner_userid,
|
||||
'deputy_userids' => $project->fresh()->deputy_userids,
|
||||
]);
|
||||
return Base::retSuccess('移交成功', ['id' => $project->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/project/adddeputy 任命项目管理员(仅负责人可操作)
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName adddeputy
|
||||
*
|
||||
* @apiParam {Number} project_id 项目ID
|
||||
* @apiParam {Number} userid 要任命的项目成员 userid
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function adddeputy()
|
||||
{
|
||||
User::auth();
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
$userid = intval(Request::input('userid'));
|
||||
|
||||
if ($userid <= 0) {
|
||||
return Base::retError('请选择有效的成员');
|
||||
}
|
||||
|
||||
$project = Project::userProject($project_id, true, 'primary');
|
||||
|
||||
$member = ProjectUser::where('project_id', $project->id)
|
||||
->where('userid', $userid)->first();
|
||||
if (!$member) {
|
||||
return Base::retError('该用户不是项目成员');
|
||||
}
|
||||
if ((int)$member->owner === ProjectUser::OWNER_PRIMARY) {
|
||||
return Base::retError('不能将负责人任命为项目管理员');
|
||||
}
|
||||
if ((int)$member->owner !== ProjectUser::OWNER_DEPUTY) {
|
||||
AbstractModel::transaction(function() use ($project, $member) {
|
||||
$member->owner = ProjectUser::OWNER_DEPUTY;
|
||||
$member->save();
|
||||
$project->syncDialogUser(); // 同步群 role
|
||||
$project->addLog('任命项目管理员', ['userid' => $member->userid]);
|
||||
});
|
||||
$project->pushMsg('detail', [
|
||||
'deputy_userids' => $project->fresh()->deputy_userids,
|
||||
]);
|
||||
}
|
||||
|
||||
return Base::retSuccess('任命成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/project/deldeputy 罢免项目管理员(仅负责人可操作)
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName deldeputy
|
||||
*
|
||||
* @apiParam {Number} project_id 项目ID
|
||||
* @apiParam {Number} userid 要罢免的项目管理员 userid
|
||||
*/
|
||||
public function deldeputy()
|
||||
{
|
||||
User::auth();
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
$userid = intval(Request::input('userid'));
|
||||
|
||||
if ($userid <= 0) {
|
||||
return Base::retError('请选择有效的成员');
|
||||
}
|
||||
|
||||
$project = Project::userProject($project_id, true, 'primary');
|
||||
|
||||
$member = ProjectUser::where('project_id', $project->id)
|
||||
->where('userid', $userid)->first();
|
||||
if (!$member) {
|
||||
return Base::retSuccess('罢免成功'); // 幂等:本来就不是成员
|
||||
}
|
||||
if ((int)$member->owner === ProjectUser::OWNER_DEPUTY) {
|
||||
AbstractModel::transaction(function() use ($project, $member) {
|
||||
$member->owner = ProjectUser::OWNER_MEMBER;
|
||||
$member->save();
|
||||
$project->syncDialogUser();
|
||||
$project->addLog('罢免项目管理员', ['userid' => $member->userid]);
|
||||
});
|
||||
$project->pushMsg('detail', [
|
||||
'deputy_userids' => $project->fresh()->deputy_userids,
|
||||
]);
|
||||
}
|
||||
|
||||
return Base::retSuccess('罢免成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/project/sort 排序任务
|
||||
*
|
||||
@ -784,7 +986,7 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
//
|
||||
$project = Project::userProject($project_id, null, true);
|
||||
$project = Project::userProject($project_id, null, 'primary');
|
||||
//
|
||||
$project->deleteProject();
|
||||
return Base::retSuccess('删除成功', ['id' => $project->id]);
|
||||
@ -813,7 +1015,7 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
// 项目
|
||||
$project = Project::userProject($project_id);
|
||||
$project = Project::findForDepartmentView($project_id);
|
||||
//
|
||||
$list = ProjectColumn::whereProjectId($project->id)
|
||||
->orderBy('sort')
|
||||
@ -1044,6 +1246,7 @@ class ProjectController extends AbstractController
|
||||
{
|
||||
$user = User::auth();
|
||||
$userid = $user->userid;
|
||||
$departmentView = UserDepartment::ownerViewContext($user, true);
|
||||
//
|
||||
$parent_id = intval(Request::input('parent_id'));
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
@ -1108,7 +1311,7 @@ class ProjectController extends AbstractController
|
||||
if ($parent_id > 0) {
|
||||
$isArchived = str_replace(['all', 'yes', 'no'], [null, false, true], $archived);
|
||||
$isDeleted = str_replace(['all', 'yes', 'no'], [null, false, true], $deleted);
|
||||
ProjectTask::userTask($parent_id, $isArchived, $isDeleted);
|
||||
ProjectTask::findForDepartmentView($parent_id, $isArchived, $isDeleted);
|
||||
$scopeAll = true;
|
||||
$archived = 'all';
|
||||
$builder->where('project_tasks.parent_id', $parent_id);
|
||||
@ -1116,17 +1319,23 @@ class ProjectController extends AbstractController
|
||||
$builder->where('project_tasks.parent_id', 0);
|
||||
}
|
||||
if ($project_id > 0) {
|
||||
Project::userProject($project_id);
|
||||
if (!UserDepartment::isDepartmentReadonlyProject($departmentView, $project_id)) {
|
||||
Project::userProject($project_id);
|
||||
}
|
||||
$scopeAll = true;
|
||||
$builder->where('project_tasks.project_id', $project_id);
|
||||
}
|
||||
if (!$scopeAll && $scope === 'all_project') {
|
||||
$scopeAll = true;
|
||||
$builder->whereIn('project_tasks.project_id', function ($query) use ($userid) {
|
||||
$query->select('project_id')
|
||||
->from('project_users')
|
||||
->where('userid', $userid);
|
||||
});
|
||||
if ($departmentView['enabled']) {
|
||||
$builder->whereIn('project_tasks.project_id', array_values(array_unique(array_merge($departmentView['own_project_ids'], $departmentView['project_ids']))));
|
||||
} else {
|
||||
$builder->whereIn('project_tasks.project_id', function ($query) use ($userid) {
|
||||
$query->select('project_id')
|
||||
->from('project_users')
|
||||
->where('userid', $userid);
|
||||
});
|
||||
}
|
||||
}
|
||||
if ($scopeAll) {
|
||||
$builder->allData();
|
||||
@ -1194,7 +1403,7 @@ class ProjectController extends AbstractController
|
||||
// 任务可见性条件
|
||||
$builder->leftJoin('project_users', function ($query) use($userid) {
|
||||
$query->on('project_tasks.project_id', '=', 'project_users.project_id');
|
||||
$query->where('project_users.owner', 1);
|
||||
$query->whereIn('project_users.owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY]);
|
||||
$query->where('project_users.userid', $userid);
|
||||
});
|
||||
$builder->leftJoin('project_task_visibility_users', function ($query) use($userid) {
|
||||
@ -1253,6 +1462,7 @@ class ProjectController extends AbstractController
|
||||
$data = $list->toArray();
|
||||
// 还原字段
|
||||
foreach($data['data'] as &$item){
|
||||
$item['department_readonly'] = UserDepartment::isDepartmentReadonlyProject($departmentView, intval($item['project_id']));
|
||||
$item['file_num'] = $item['_file_num'] ?: 0;
|
||||
$item['msg_num'] = $item['_msg_num'] ?: 0;
|
||||
$item['sub_num'] = $item['_sub_num'] ?: 0;
|
||||
@ -1832,15 +2042,18 @@ class ProjectController extends AbstractController
|
||||
public function task__one()
|
||||
{
|
||||
$user = User::auth();
|
||||
$departmentView = UserDepartment::ownerViewContext($user, true);
|
||||
//
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$archived = Request::input('archived', 'no');
|
||||
//
|
||||
$isArchived = str_replace(['all', 'yes', 'no'], [null, false, true], $archived);
|
||||
$task = ProjectTask::userTask($task_id, $isArchived, true, ['taskUser', 'taskTag']);
|
||||
$task = ProjectTask::findForDepartmentView($task_id, $isArchived, true, ['taskUser', 'taskTag']);
|
||||
// 项目可见性
|
||||
$project_userid = ProjectUser::whereProjectId($task->project_id)->whereOwner(1)->value('userid'); // 项目负责人
|
||||
if ($task->visibility != 1 && $user->userid != $project_userid) {
|
||||
$projectOwnerids = ProjectUser::whereProjectId($task->project_id)
|
||||
->whereIn('owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY])
|
||||
->pluck('userid')->map(fn($v) => (int)$v)->toArray(); // 项目负责人(含项目管理员)
|
||||
if ($task->visibility != 1 && !in_array($user->userid, $projectOwnerids)) {
|
||||
$taskUserids = ProjectTaskUser::whereTaskId($task_id)->pluck('userid')->toArray(); //任务负责人、协助人
|
||||
$subTaskUserids = ProjectTaskUser::whereTaskPid($task_id)->pluck('userid')->toArray(); //子任务负责人、协助人
|
||||
$visibleUserids = ProjectTaskVisibilityUser::whereTaskId($task_id)->pluck('userid')->toArray(); //可见人
|
||||
@ -1851,6 +2064,7 @@ class ProjectController extends AbstractController
|
||||
}
|
||||
//
|
||||
$data = $task->toArray();
|
||||
$data['department_readonly'] = UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id));
|
||||
$data['project_name'] = $task->project?->name;
|
||||
$data['column_name'] = $task->projectColumn?->name;
|
||||
$data['visibility_appointor'] = $task->visibility == 1 ? [0] : ProjectTaskVisibilityUser::whereTaskId($task_id)->pluck('userid');
|
||||
@ -1879,7 +2093,7 @@ class ProjectController extends AbstractController
|
||||
return Base::retError('参数错误', ['task_id' => $task_id]);
|
||||
}
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id);
|
||||
$task = ProjectTask::findForDepartmentView($task_id);
|
||||
//
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $task->id,
|
||||
@ -1911,7 +2125,7 @@ class ProjectController extends AbstractController
|
||||
return Base::retError('参数错误', ['task_id' => $task_id]);
|
||||
}
|
||||
|
||||
$task = ProjectTask::userTask($task_id, null);
|
||||
$task = ProjectTask::findForDepartmentView($task_id, null);
|
||||
|
||||
$relations = ProjectTaskRelation::whereTaskId($task->id)
|
||||
->orderByDesc('updated_at')
|
||||
@ -1929,7 +2143,7 @@ class ProjectController extends AbstractController
|
||||
$relatedTasks = [];
|
||||
foreach ($relatedTaskIds as $relatedId) {
|
||||
try {
|
||||
$relatedTask = ProjectTask::userTask($relatedId, null, true, ['project', 'projectColumn']);
|
||||
$relatedTask = ProjectTask::findForDepartmentView($relatedId, null, true, ['project', 'projectColumn']);
|
||||
|
||||
$flowItemParts = explode('|', $relatedTask->flow_item_name ?: '');
|
||||
$flowItemStatus = $flowItemParts[0] ?? '';
|
||||
@ -2055,7 +2269,7 @@ class ProjectController extends AbstractController
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$history_id = intval(Request::input('history_id'));
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, null);
|
||||
$task = ProjectTask::findForDepartmentView($task_id, null);
|
||||
//
|
||||
if ($history_id > 0) {
|
||||
$taskContent = ProjectTaskContent::whereTaskId($task->id)->whereId($history_id)->first();
|
||||
@ -2095,7 +2309,7 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, null);
|
||||
$task = ProjectTask::findForDepartmentView($task_id, null);
|
||||
//
|
||||
$data = ProjectTaskContent::select(['id', 'task_id', 'desc', 'userid', 'created_at'])
|
||||
->whereTaskId($task->id)
|
||||
@ -2124,7 +2338,7 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id, null);
|
||||
$task = ProjectTask::findForDepartmentView($task_id, null);
|
||||
//
|
||||
return Base::retSuccess('success', $task->taskFile);
|
||||
}
|
||||
@ -2213,7 +2427,7 @@ class ProjectController extends AbstractController
|
||||
$data = $file->toArray();
|
||||
$data['path'] = $file->getRawOriginal('path');
|
||||
//
|
||||
ProjectTask::userTask($file->task_id, null);
|
||||
ProjectTask::findForDepartmentView($file->task_id, null);
|
||||
//
|
||||
UserRecentItem::record(
|
||||
$user->userid,
|
||||
@ -2254,7 +2468,7 @@ class ProjectController extends AbstractController
|
||||
abort_if(empty($file), 403, "This file not exist.");
|
||||
//
|
||||
try {
|
||||
ProjectTask::userTask($file->task_id, null);
|
||||
ProjectTask::findForDepartmentView($file->task_id, null);
|
||||
} catch (\Throwable $e) {
|
||||
abort(403, $e->getMessage() ?: "This file not support download.");
|
||||
}
|
||||
@ -2347,7 +2561,9 @@ class ProjectController extends AbstractController
|
||||
if ($data['visibility'] == 1) {
|
||||
$data['is_visible'] = 1;
|
||||
} else {
|
||||
$projectOwner = ProjectUser::whereProjectId($task->project_id)->whereOwner(1)->pluck('userid')->toArray(); // 项目负责人
|
||||
$projectOwner = ProjectUser::whereProjectId($task->project_id)
|
||||
->whereIn('owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY])
|
||||
->pluck('userid')->toArray(); // 项目负责人(含项目管理员)
|
||||
$taskOwnerAndAssists = ProjectTaskUser::select(['userid', 'owner'])->whereTaskId($data['id'])->pluck('userid')->toArray();
|
||||
$visibleIds = array_merge($projectOwner, $taskOwnerAndAssists);
|
||||
$data['is_visible'] = in_array($user->userid, $visibleIds) ? 1 : 0;
|
||||
@ -2355,6 +2571,21 @@ class ProjectController extends AbstractController
|
||||
|
||||
$task->pushMsg('add', $data);
|
||||
$task->taskPush(null, 0);
|
||||
|
||||
// 应用任务模板使用统计(不影响主流程;非成员、模板已删除或共享模板已关闭时静默忽略)
|
||||
$templateId = intval(Request::input('template_id', 0));
|
||||
if ($templateId > 0) {
|
||||
$tpl = ProjectTaskTemplate::find($templateId);
|
||||
if ($tpl) {
|
||||
$isMember = ProjectUser::where('project_id', $tpl->project_id)
|
||||
->where('userid', $user->userid)->exists();
|
||||
$shareEnabled = ($project->task_template_share ?: 'open') === 'open';
|
||||
if ($isMember && ($tpl->project_id == $project->id || $shareEnabled)) {
|
||||
$tpl->incrementUsage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Base::retSuccess('添加成功', $data);
|
||||
}
|
||||
|
||||
@ -2398,7 +2629,10 @@ class ProjectController extends AbstractController
|
||||
]);
|
||||
$data = ProjectTask::oneTask($task->id);
|
||||
$pushUserIds = ProjectTaskUser::whereTaskId($task->id)->pluck('userid')->toArray();
|
||||
$pushUserIds[] = ProjectUser::whereProjectId($task->project_id)->whereOwner(1)->value('userid');
|
||||
$ownerids = ProjectUser::whereProjectId($task->project_id)
|
||||
->whereIn('owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY])
|
||||
->pluck('userid')->toArray();
|
||||
$pushUserIds = array_merge($pushUserIds, $ownerids);
|
||||
foreach ($pushUserIds as $userId) {
|
||||
$task->pushMsg('add', $data, $userId);
|
||||
}
|
||||
@ -3181,7 +3415,7 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
//
|
||||
$project = Project::userProject($project_id, true);
|
||||
$project = Project::findForDepartmentView($project_id, true);
|
||||
//
|
||||
$list = ProjectFlow::with(['ProjectFlowItem'])->whereProjectId($project->id)->get();
|
||||
return Base::retSuccess('success', $list);
|
||||
@ -3280,10 +3514,10 @@ class ProjectController extends AbstractController
|
||||
//
|
||||
$builder = ProjectLog::select(["*"]);
|
||||
if ($task_id > 0) {
|
||||
$task = ProjectTask::userTask($task_id, null);
|
||||
$task = ProjectTask::findForDepartmentView($task_id, null);
|
||||
$builder->whereTaskId($task->id);
|
||||
} else {
|
||||
$project = Project::userProject($project_id);
|
||||
$project = Project::findForDepartmentView($project_id);
|
||||
$builder->with(['projectTask:id,parent_id,name'])->whereProjectId($project->id)->whereTaskOnly(0);
|
||||
}
|
||||
//
|
||||
@ -3455,6 +3689,127 @@ class ProjectController extends AbstractController
|
||||
return Base::retSuccess('success', $templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/template_visible 当前用户跨项目可见的全部任务模板
|
||||
*
|
||||
* @apiDescription 返回当前用户加入的所有项目下的任务模板。当前项目的模板优先排序。
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__template_visible
|
||||
*
|
||||
* @apiParam {Number} [current_project_id] 当前项目 ID(用于排序优先;可空)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1 正确、0 错误)
|
||||
* @apiSuccess {String} msg 返回信息
|
||||
* @apiSuccess {Object[]} data 模板列表,每条包含 project_id, project_name, name, title, content, sort, is_default, userid, use_count, last_used_at
|
||||
*/
|
||||
public function task__template_visible()
|
||||
{
|
||||
$user = User::auth();
|
||||
$currentProjectId = intval(Request::input('current_project_id', 0));
|
||||
|
||||
$projectIds = ProjectUser::where('userid', $user->userid)->pluck('project_id');
|
||||
$currentProject = $currentProjectId > 0 ? Project::find($currentProjectId) : null;
|
||||
if ($currentProject && ($currentProject->task_template_share ?: 'open') === 'close') {
|
||||
$projectIds = collect($projectIds)->filter(fn($id) => intval($id) === $currentProjectId)->values();
|
||||
}
|
||||
|
||||
$rows = ProjectTaskTemplate::with(['project:id,name'])
|
||||
->whereIn('project_id', $projectIds)
|
||||
->orderByRaw('project_id = ? DESC', [$currentProjectId])
|
||||
->orderBy('sort')
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->map(function ($tpl) {
|
||||
return [
|
||||
'id' => $tpl->id,
|
||||
'project_id' => $tpl->project_id,
|
||||
'project_name' => $tpl->project->name ?? '',
|
||||
'name' => $tpl->name,
|
||||
'title' => $tpl->title,
|
||||
'content' => $tpl->content,
|
||||
'sort' => $tpl->sort,
|
||||
'is_default' => $tpl->is_default,
|
||||
'userid' => $tpl->userid,
|
||||
'use_count' => $tpl->use_count,
|
||||
'last_used_at' => $tpl->last_used_at,
|
||||
];
|
||||
});
|
||||
|
||||
return Base::retSuccess('success', $rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/task/template_search 跨项目模板搜索分页
|
||||
*
|
||||
* @apiDescription "更多"弹层用。返回当前用户跨项目可见模板,支持关键字 + 分页。
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__template_search
|
||||
*
|
||||
* @apiParam {String} [keyword] 关键字(在 name/title/content 上模糊匹配)
|
||||
* @apiParam {Number} [current_project_id] 当前项目 ID(共享模板关闭时仅返回本项目模板)
|
||||
* @apiParam {Number} [page=1] 页码
|
||||
* @apiParam {Number} [page_size=20] 每页条数(最大 50)
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码
|
||||
* @apiSuccess {Object} data 含 total / page / page_size / items
|
||||
*/
|
||||
public function task__template_search()
|
||||
{
|
||||
$user = User::auth();
|
||||
$keyword = trim((string) Request::input('keyword', ''));
|
||||
$currentProjectId = intval(Request::input('current_project_id', 0));
|
||||
$page = max(1, intval(Request::input('page', 1)));
|
||||
$pageSize = min(50, max(1, intval(Request::input('page_size', 20))));
|
||||
|
||||
$projectIds = ProjectUser::where('userid', $user->userid)->pluck('project_id');
|
||||
$currentProject = $currentProjectId > 0 ? Project::find($currentProjectId) : null;
|
||||
if ($currentProject && ($currentProject->task_template_share ?: 'open') === 'close') {
|
||||
$projectIds = collect($projectIds)->filter(fn($id) => intval($id) === $currentProjectId)->values();
|
||||
}
|
||||
|
||||
$q = ProjectTaskTemplate::with(['project:id,name', 'user:userid,nickname'])
|
||||
->whereIn('project_id', $projectIds);
|
||||
|
||||
if ($keyword !== '') {
|
||||
$like = '%' . $keyword . '%';
|
||||
$q->where(function ($qq) use ($like) {
|
||||
$qq->where('name', 'like', $like)
|
||||
->orWhere('title', 'like', $like)
|
||||
->orWhere('content', 'like', $like);
|
||||
});
|
||||
}
|
||||
|
||||
$total = (clone $q)->count();
|
||||
$items = $q->orderByDesc('use_count')
|
||||
->orderByDesc('last_used_at')
|
||||
->orderByDesc('created_at')
|
||||
->forPage($page, $pageSize)
|
||||
->get()
|
||||
->map(function ($tpl) {
|
||||
return [
|
||||
'id' => $tpl->id,
|
||||
'project_id' => $tpl->project_id,
|
||||
'project_name' => $tpl->project->name ?? '',
|
||||
'name' => $tpl->name,
|
||||
'title' => $tpl->title,
|
||||
'content' => $tpl->content,
|
||||
'use_count' => $tpl->use_count,
|
||||
'userid' => $tpl->userid,
|
||||
'user_name' => $tpl->user->nickname ?? '',
|
||||
'last_used_at' => $tpl->last_used_at,
|
||||
];
|
||||
});
|
||||
|
||||
return Base::retSuccess('success', [
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'page_size' => $pageSize,
|
||||
'items' => $items,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/project/task/template_save 保存任务模板
|
||||
*
|
||||
|
||||
@ -94,6 +94,7 @@ class SystemController extends AbstractController
|
||||
'unclaimed_task_reminder',
|
||||
'unclaimed_task_reminder_time',
|
||||
'task_ai_auto_analyze',
|
||||
'department_owner_project_view',
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
@ -148,6 +149,7 @@ class SystemController extends AbstractController
|
||||
$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'] ?: 'close';
|
||||
$setting['server_timezone'] = config('app.timezone');
|
||||
$setting['server_version'] = Base::getVersion();
|
||||
//
|
||||
|
||||
@ -404,9 +404,22 @@ class UsersController extends AbstractController
|
||||
$data['nickname_original'] = $user->getRawOriginal('nickname');
|
||||
$data['department_name'] = $user->getDepartmentName();
|
||||
$data['department_owner'] = UserDepartment::where('parent_id',0)->where('owner_userid', $user->userid)->exists(); // 适用默认部门下第1级负责人才能添加部门OKR
|
||||
$data['managed_departments'] = UserDepartment::getManagedDepartments($user->userid)->toArray();
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/users/info/managed_departments 获取我可切换负责人视角的部门列表
|
||||
*/
|
||||
public function info__managed_departments()
|
||||
{
|
||||
$user = User::auth();
|
||||
if (Base::settingFind('system', 'department_owner_project_view', 'close') !== 'open') {
|
||||
return Base::retSuccess('success', []);
|
||||
}
|
||||
return Base::retSuccess('success', UserDepartment::getManagedDepartments($user->userid));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/users/info/departments 获取我的部门列表
|
||||
*
|
||||
@ -2145,6 +2158,65 @@ class UsersController extends AbstractController
|
||||
return Base::retSuccess($id > 0 ? '保存成功' : '新建成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/users/department/adddeputy 任命部门管理员(限管理员)
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName department__adddeputy
|
||||
*
|
||||
* @apiParam {Number} id 部门 id
|
||||
* @apiParam {Number} userid 部门管理员 userid
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
*/
|
||||
public function department__adddeputy()
|
||||
{
|
||||
User::auth('admin');
|
||||
$id = intval(Request::input('id'));
|
||||
$userid = intval(Request::input('userid'));
|
||||
|
||||
$dept = UserDepartment::find($id);
|
||||
if (empty($dept)) {
|
||||
return Base::retError('部门不存在或已被删除');
|
||||
}
|
||||
|
||||
// ApiException 由框架统一捕获并 retError 转换
|
||||
$dept->addDeputy($userid);
|
||||
|
||||
Cache::forever("UserDepartment::rand", Base::generatePassword());
|
||||
return Base::retSuccess('任命成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/users/department/deldeputy 罢免部门管理员(限管理员)
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup users
|
||||
* @apiName department__deldeputy
|
||||
*
|
||||
* @apiParam {Number} id 部门 id
|
||||
* @apiParam {Number} userid 要罢免的部门管理员 userid
|
||||
*/
|
||||
public function department__deldeputy()
|
||||
{
|
||||
User::auth('admin');
|
||||
$id = intval(Request::input('id'));
|
||||
$userid = intval(Request::input('userid'));
|
||||
|
||||
$dept = UserDepartment::find($id);
|
||||
if (empty($dept)) {
|
||||
return Base::retError('部门不存在或已被删除');
|
||||
}
|
||||
|
||||
$dept->delDeputy($userid);
|
||||
Cache::forever("UserDepartment::rand", Base::generatePassword());
|
||||
return Base::retSuccess('罢免成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/users/department/del 删除部门(限管理员)
|
||||
*
|
||||
@ -3213,7 +3285,7 @@ class UsersController extends AbstractController
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
//
|
||||
ProjectTask::userTask($task_id, null, null);
|
||||
ProjectTask::findForDepartmentView($task_id, null, null);
|
||||
//
|
||||
UserTaskBrowse::recordBrowse($user->userid, $task_id);
|
||||
//
|
||||
|
||||
@ -20,9 +20,7 @@ use Illuminate\Support\Facades\DB;
|
||||
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelAppend()
|
||||
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelHidden()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|static with($relations)
|
||||
* @method static \Illuminate\Database\Query\Builder|static select($columns = [])
|
||||
* @method static \Illuminate\Database\Query\Builder|static whereIn($column, $values, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder|static whereNotIn($column, $values, $boolean = 'and')
|
||||
* @method static \Illuminate\Pagination\LengthAwarePaginator paginate(callable $callback)
|
||||
* @method int change(array $array)
|
||||
* @method int remove()
|
||||
* @mixin \Eloquent
|
||||
|
||||
@ -22,6 +22,9 @@ use Request;
|
||||
* @property int|null $personal 是否个人项目
|
||||
* @property string|null $archive_method 自动归档方式
|
||||
* @property int|null $archive_days 自动归档天数
|
||||
* @property string|null $ai_auto_analyze AI自动分析
|
||||
* @property string|null $task_template_share 共享模板开关
|
||||
* @property string|null $department_owner_view 部门负责人视角可见开关
|
||||
* @property string|null $user_simple 成员总数|1,2,3
|
||||
* @property int|null $dialog_id 聊天会话ID
|
||||
* @property \Illuminate\Support\Carbon|null $archived_at 归档时间
|
||||
@ -77,6 +80,7 @@ class Project extends AbstractModel
|
||||
|
||||
protected $appends = [
|
||||
'owner_userid',
|
||||
'deputy_userids',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -92,6 +96,58 @@ class Project extends AbstractModel
|
||||
return $this->appendattrs['owner_userid'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目管理员 userid 列表
|
||||
* @return array
|
||||
*/
|
||||
public function getDeputyUseridsAttribute(): array
|
||||
{
|
||||
if (empty($this->id)) {
|
||||
return [];
|
||||
}
|
||||
return ProjectUser::whereProjectId($this->id)
|
||||
->whereOwner(ProjectUser::OWNER_DEPUTY)
|
||||
->pluck('userid')
|
||||
->map(fn($v) => (int)$v)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否项目负责人(与 project_users.owner=1 一致)
|
||||
*/
|
||||
public function isPrimaryOwner($userid): bool
|
||||
{
|
||||
if (empty($this->id) || $userid <= 0) {
|
||||
return false;
|
||||
}
|
||||
return ProjectUser::whereProjectId($this->id)
|
||||
->whereUserid($userid)
|
||||
->whereOwner(ProjectUser::OWNER_PRIMARY)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否项目管理员(与 project_users.owner=2 一致)
|
||||
*/
|
||||
public function isDeputyOwner($userid): bool
|
||||
{
|
||||
if (empty($this->id) || $userid <= 0) {
|
||||
return false;
|
||||
}
|
||||
return ProjectUser::whereProjectId($this->id)
|
||||
->whereUserid($userid)
|
||||
->whereOwner(ProjectUser::OWNER_DEPUTY)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否负责人(含项目管理员)
|
||||
*/
|
||||
public function isOwner($userid): bool
|
||||
{
|
||||
return $this->isPrimaryOwner($userid) || $this->isDeputyOwner($userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
@ -227,21 +283,40 @@ class Project extends AbstractModel
|
||||
return;
|
||||
}
|
||||
AbstractModel::transaction(function() {
|
||||
$userids = $this->relationUserids();
|
||||
// 拉所有项目成员 + 各自 owner 值
|
||||
$userOwnerMap = ProjectUser::whereProjectId($this->id)
|
||||
->pluck('owner', 'userid');
|
||||
$userids = $userOwnerMap->keys()->map(fn($v) => (int)$v)->toArray();
|
||||
foreach ($userids as $userid) {
|
||||
$owner = (int)$userOwnerMap[$userid];
|
||||
// 巧合:编码完全一致 owner 0/1/2 → role 0/1/2
|
||||
$role = $owner;
|
||||
WebSocketDialogUser::updateInsert([
|
||||
'dialog_id' => $this->dialog_id,
|
||||
'userid' => $userid,
|
||||
], [
|
||||
'important' => 1
|
||||
], function () use ($userid) {
|
||||
'important' => 1,
|
||||
'role' => $role,
|
||||
], function () use ($userid, $role) {
|
||||
return [
|
||||
'important' => 1,
|
||||
'role' => $role,
|
||||
'bot' => User::isBot($userid) ? 1 : 0,
|
||||
];
|
||||
});
|
||||
}
|
||||
WebSocketDialogUser::whereDialogId($this->dialog_id)->whereNotIn('userid', $userids)->whereImportant(1)->remove();
|
||||
WebSocketDialogUser::whereDialogId($this->dialog_id)
|
||||
->whereNotIn('userid', $userids)
|
||||
->whereImportant(1)
|
||||
->remove();
|
||||
// 同步 dialog.owner_id 到主负责人(owner=1):前端「群主」标签依赖此字段,
|
||||
// 必须随项目主负责人变更(含用户离职转移)一起刷新,否则会显示已离职用户
|
||||
$primaryUserid = $userOwnerMap->search(ProjectUser::OWNER_PRIMARY);
|
||||
if ($primaryUserid !== false && (int)$primaryUserid > 0) {
|
||||
WebSocketDialog::whereId($this->dialog_id)
|
||||
->where('owner_id', '!=', (int)$primaryUserid)
|
||||
->update(['owner_id' => (int)$primaryUserid]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -378,7 +453,7 @@ class Project extends AbstractModel
|
||||
// 处理所有者权限
|
||||
if (isset($data['owner'])) {
|
||||
$owners = ProjectUser::whereProjectId($data['id'])
|
||||
->whereOwner(1)
|
||||
->whereIn('owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY])
|
||||
->pluck('userid')
|
||||
->toArray();
|
||||
$recipients = [
|
||||
@ -599,7 +674,7 @@ class Project extends AbstractModel
|
||||
$column['project_id'] = $project->id;
|
||||
ProjectColumn::createInstance($column)->save();
|
||||
}
|
||||
$dialog = WebSocketDialog::createGroup($project->name, $project->userid, 'project');
|
||||
$dialog = WebSocketDialog::createGroup($project->name, $project->userid, 'project', $project->userid);
|
||||
if (empty($dialog)) {
|
||||
throw new ApiException('创建项目聊天室失败');
|
||||
}
|
||||
@ -621,7 +696,9 @@ class Project extends AbstractModel
|
||||
* 获取项目信息(用于判断会员是否存在项目内)
|
||||
* @param int $project_id
|
||||
* @param null|bool $archived true:仅限未归档, false:仅限已归档, null:不限制
|
||||
* @param null|bool $mustOwner true:仅限项目负责人, false:仅限非项目负责人, null:不限制
|
||||
* @param null|bool|string $mustOwner true:负责人或项目管理员都可(共享操作);
|
||||
* 'primary':仅负责人(转让/删除/任命项目管理员等独占操作);
|
||||
* false:仅限非负责人;null:不限制
|
||||
* @return self
|
||||
*/
|
||||
public static function userProject($project_id, $archived = true, $mustOwner = null)
|
||||
@ -639,9 +716,39 @@ class Project extends AbstractModel
|
||||
if ($mustOwner === true && !$project->owner) {
|
||||
throw new ApiException('仅限项目负责人操作', [ 'project_id' => $project_id ]);
|
||||
}
|
||||
if ($mustOwner === 'primary' && (int)$project->owner !== 1) {
|
||||
throw new ApiException('仅限项目负责人操作', [ 'project_id' => $project_id ]);
|
||||
}
|
||||
if ($mustOwner === false && $project->owner) {
|
||||
throw new ApiException('禁止项目负责人操作', [ 'project_id' => $project_id ]);
|
||||
}
|
||||
return $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目(含部门负责人只读视角兜底)
|
||||
* @param int $project_id
|
||||
* @param null|bool $archived true:仅限未归档, false:仅限已归档, null:不限制
|
||||
* @param null|bool|string $mustOwner 仅限 null 时尝试部门只读视角
|
||||
* @return self
|
||||
*/
|
||||
public static function findForDepartmentView($project_id, $archived = true, $mustOwner = null)
|
||||
{
|
||||
$user = User::auth();
|
||||
$departmentView = UserDepartment::ownerViewContext($user, true);
|
||||
if (UserDepartment::isDepartmentReadonlyProject($departmentView, intval($project_id)) && $mustOwner === null) {
|
||||
$project = self::allData()->where('projects.id', intval($project_id))->first();
|
||||
if (empty($project)) {
|
||||
throw new ApiException('项目不存在或已被删除', [ 'project_id' => $project_id ], -4001);
|
||||
}
|
||||
if ($archived === true && $project->archived_at != null) {
|
||||
throw new ApiException('项目已归档', [ 'project_id' => $project_id ], -4001);
|
||||
}
|
||||
if ($archived === false && $project->archived_at == null) {
|
||||
throw new ApiException('项目未归档', [ 'project_id' => $project_id ]);
|
||||
}
|
||||
return $project;
|
||||
}
|
||||
return self::userProject($project_id, $archived, $mustOwner);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1991,7 +1991,9 @@ class ProjectTask extends AbstractModel
|
||||
'dialog_id' => $this->dialog_id,
|
||||
];
|
||||
//
|
||||
$projectOwnerids = ProjectUser::whereProjectId($this->project_id)->whereOwner(1)->pluck('userid')->toArray(); // 项目负责人
|
||||
$projectOwnerids = ProjectUser::whereProjectId($this->project_id)
|
||||
->whereIn('owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY])
|
||||
->pluck('userid')->toArray(); // 项目负责人(含项目管理员)
|
||||
//
|
||||
$array = [];
|
||||
if (empty($userids)) {
|
||||
@ -2256,6 +2258,40 @@ class ProjectTask extends AbstractModel
|
||||
return $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务(含部门负责人只读视角兜底)
|
||||
* @param int $task_id
|
||||
* @param null|bool $archived true:仅限未归档, false:仅限已归档, null:不限制
|
||||
* @param null|bool $trashed true:仅限未删除, false:仅限已删除, null:不限制
|
||||
* @param array $with
|
||||
* @return self
|
||||
*/
|
||||
public static function findForDepartmentView($task_id, $archived = true, $trashed = true, $with = [])
|
||||
{
|
||||
$user = User::auth();
|
||||
$departmentView = UserDepartment::ownerViewContext($user, true);
|
||||
if ($departmentView['enabled']) {
|
||||
$builder = self::with($with)->allData()->where('project_tasks.id', intval($task_id));
|
||||
if ($trashed === false) {
|
||||
$builder->onlyTrashed();
|
||||
} elseif ($trashed === null) {
|
||||
$builder->withTrashed();
|
||||
}
|
||||
$task = $builder->first();
|
||||
// 仅"全员可见"(visibility=1)的任务走负责人只读视角;指定成员可见的任务交由 userTask 按可见性校验
|
||||
if (!empty($task) && intval($task->visibility) === 1 && UserDepartment::isDepartmentReadonlyProject($departmentView, intval($task->project_id))) {
|
||||
if ($archived === true && $task->archived_at != null) {
|
||||
throw new ApiException('任务已归档', ['task_id' => $task_id]);
|
||||
}
|
||||
if ($archived === false && $task->archived_at == null) {
|
||||
throw new ApiException('任务未归档', ['task_id' => $task_id]);
|
||||
}
|
||||
return $task;
|
||||
}
|
||||
}
|
||||
return self::userTask($task_id, $archived, $trashed, $with);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建指定周期内的未完成任务查询(用于周报/日报等)
|
||||
* @param int $userid
|
||||
|
||||
@ -13,6 +13,8 @@ namespace App\Models;
|
||||
* @property int $sort 排序
|
||||
* @property int $is_default 是否默认模板
|
||||
* @property int $userid 创建人
|
||||
* @property int $use_count 累计使用次数
|
||||
* @property \Illuminate\Support\Carbon|null $last_used_at 最近一次使用时间
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Project $project
|
||||
@ -52,7 +54,18 @@ class ProjectTaskTemplate extends AbstractModel
|
||||
'content',
|
||||
'sort',
|
||||
'is_default',
|
||||
'userid'
|
||||
'userid',
|
||||
'use_count',
|
||||
'last_used_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'last_used_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -74,4 +87,17 @@ class ProjectTaskTemplate extends AbstractModel
|
||||
{
|
||||
return $this->belongsTo(User::class, 'userid');
|
||||
}
|
||||
|
||||
/**
|
||||
* 原子递增使用次数并刷新最近使用时间。
|
||||
*/
|
||||
public function incrementUsage(): void
|
||||
{
|
||||
$this->newQuery()
|
||||
->where('id', $this->id)
|
||||
->update([
|
||||
'use_count' => \DB::raw('use_count + 1'),
|
||||
'last_used_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,6 +37,36 @@ use App\Module\Base;
|
||||
*/
|
||||
class ProjectUser extends AbstractModel
|
||||
{
|
||||
/** @var int 普通成员编码 */
|
||||
const OWNER_MEMBER = 0;
|
||||
/** @var int 项目负责人编码 */
|
||||
const OWNER_PRIMARY = 1;
|
||||
/** @var int 项目管理员编码 */
|
||||
const OWNER_DEPUTY = 2;
|
||||
|
||||
/**
|
||||
* 是否项目负责人(owner=1)
|
||||
*/
|
||||
public function isPrimaryOwner(): bool
|
||||
{
|
||||
return (int)$this->owner === self::OWNER_PRIMARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否项目管理员(owner=2)
|
||||
*/
|
||||
public function isDeputyOwner(): bool
|
||||
{
|
||||
return (int)$this->owner === self::OWNER_DEPUTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否负责人(含项目管理员)
|
||||
*/
|
||||
public function isOwner(): bool
|
||||
{
|
||||
return $this->isPrimaryOwner() || $this->isDeputyOwner();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
@ -61,12 +91,19 @@ class ProjectUser extends AbstractModel
|
||||
foreach ($list as $item) {
|
||||
$row = self::whereProjectId($item->project_id)->whereUserid($newUserid)->first();
|
||||
if ($row) {
|
||||
// 已存在则删除原数据,判断改变已存在的数据
|
||||
$row->owner = max($row->owner, $item->owner);
|
||||
// 已存在:仅当离职用户是项目负责人(owner=1)时把接收人升为项目负责人;
|
||||
// 离职用户是项目管理员(owner=2)时不传项目管理员身份给接收人(spec:项目管理员不替补)
|
||||
if ((int)$item->owner === self::OWNER_PRIMARY) {
|
||||
$row->owner = self::OWNER_PRIMARY;
|
||||
}
|
||||
// owner=2/0:保留接收人原有 owner 值不变
|
||||
$row->save();
|
||||
$item->delete();
|
||||
} else {
|
||||
// 不存在则改变原数据
|
||||
// 不存在:转移时如果离职用户是项目管理员,降级为普通成员(不带项目管理员身份过户给接收人)
|
||||
if ((int)$item->owner === self::OWNER_DEPUTY) {
|
||||
$item->owner = self::OWNER_MEMBER;
|
||||
}
|
||||
$item->userid = $newUserid;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
@ -3,7 +3,9 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use Cache;
|
||||
use Request;
|
||||
|
||||
/**
|
||||
* App\Models\UserDepartment
|
||||
@ -35,6 +37,10 @@ use Cache;
|
||||
*/
|
||||
class UserDepartment extends AbstractModel
|
||||
{
|
||||
protected $appends = [
|
||||
'deputy_userids',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取所有父级部门
|
||||
* @return array
|
||||
@ -50,6 +56,55 @@ class UserDepartment extends AbstractModel
|
||||
return $parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门管理员 userid 列表
|
||||
* @return array
|
||||
*/
|
||||
public function getDeputyUseridsAttribute(): array
|
||||
{
|
||||
if (empty($this->id)) {
|
||||
return [];
|
||||
}
|
||||
return \DB::table('user_department_owners')
|
||||
->where('department_id', $this->id)
|
||||
->pluck('userid')
|
||||
->map(fn($v) => (int)$v)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否部门负责人(与 owner_userid 一致)
|
||||
*/
|
||||
public function isPrimaryOwner($userid): bool
|
||||
{
|
||||
if (empty($this->id) || $userid <= 0) {
|
||||
return false;
|
||||
}
|
||||
return (int)$this->owner_userid === (int)$userid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否部门管理员(在 user_department_owners 表里)
|
||||
*/
|
||||
public function isDeputyOwner($userid): bool
|
||||
{
|
||||
if (empty($this->id) || $userid <= 0) {
|
||||
return false;
|
||||
}
|
||||
return \DB::table('user_department_owners')
|
||||
->where('department_id', $this->id)
|
||||
->where('userid', $userid)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否负责人(含部门管理员)
|
||||
*/
|
||||
public function isOwner($userid): bool
|
||||
{
|
||||
return $this->isPrimaryOwner($userid) || $this->isDeputyOwner($userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存部门
|
||||
* @param $data
|
||||
@ -65,18 +120,38 @@ class UserDepartment extends AbstractModel
|
||||
}
|
||||
$this->updateInstance($data);
|
||||
//
|
||||
// 防御:新负责人若残留在 user_department_owners 中(如曾是该部门管理员),清理掉
|
||||
// 否则后续 delDeputy / 罢免接口会把当前部门负责人误移出部门
|
||||
if ($this->id && (int)$this->owner_userid > 0) {
|
||||
\DB::table('user_department_owners')
|
||||
->where('department_id', $this->id)
|
||||
->where('userid', (int)$this->owner_userid)
|
||||
->delete();
|
||||
}
|
||||
//
|
||||
if ($this->dialog_id > 0) {
|
||||
// 已有群
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
if ($dialog) {
|
||||
$oldOwnerId = (int)$dialog->owner_id;
|
||||
$dialog->name = $this->name;
|
||||
$dialog->owner_id = $this->owner_userid;
|
||||
if ($dialog->save()) {
|
||||
$dialog->joinGroup($this->owner_userid, 0, true);
|
||||
// 同步 role:原负责人 role=0、新负责人 role=1(部门管理员 role=2 保留不动)
|
||||
if ($oldOwnerId > 0 && $oldOwnerId !== (int)$this->owner_userid) {
|
||||
WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', $oldOwnerId)
|
||||
->update(['role' => 0]);
|
||||
}
|
||||
WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', $this->owner_userid)
|
||||
->update(['role' => 1]);
|
||||
$dialog->pushMsg("groupUpdate", [
|
||||
'id' => $dialog->id,
|
||||
'name' => $dialog->name,
|
||||
'owner_id' => $dialog->owner_id,
|
||||
'deputy_ids' => $dialog->fresh()->deputy_ids,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -86,16 +161,33 @@ class UserDepartment extends AbstractModel
|
||||
if (empty($dialog)) {
|
||||
throw new ApiException("选择现有聊天群不存在");
|
||||
}
|
||||
$oldOwnerId = (int)$dialog->owner_id;
|
||||
$dialog->name = $this->name;
|
||||
$dialog->owner_id = $this->owner_userid;
|
||||
$dialog->group_type = 'department';
|
||||
if ($dialog->save()) {
|
||||
$dialog->joinGroup($this->owner_userid, 0, true);
|
||||
// 同步 role:原负责人 role=0、新负责人 role=1、原部门管理员 role=0
|
||||
// 原部门管理员清零:避免 dialog_users.role=2 与 user_department_owners 不一致
|
||||
// (部门管理员关系不带过来,须通过 addDeputy 显式重新任命)
|
||||
if ($oldOwnerId > 0 && $oldOwnerId !== (int)$this->owner_userid) {
|
||||
WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', $oldOwnerId)
|
||||
->update(['role' => 0]);
|
||||
}
|
||||
WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', '!=', $this->owner_userid)
|
||||
->where('role', 2)
|
||||
->update(['role' => 0]);
|
||||
WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', $this->owner_userid)
|
||||
->update(['role' => 1]);
|
||||
$dialog->pushMsg("groupUpdate", [
|
||||
'id' => $dialog->id,
|
||||
'name' => $dialog->name,
|
||||
'owner_id' => $dialog->owner_id,
|
||||
'group_type' => $dialog->group_type,
|
||||
'deputy_ids' => $dialog->fresh()->deputy_ids,
|
||||
]);
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'notice', [
|
||||
'notice' => User::nickname() . " 将此群改为部门群"
|
||||
@ -116,6 +208,12 @@ class UserDepartment extends AbstractModel
|
||||
$oldUser->department = array_diff($oldUser->department, [$this->id]);
|
||||
$oldUser->department = "," . implode(",", $oldUser->department) . ",";
|
||||
$oldUser->save();
|
||||
// 原主从 users.department 移除后也要退出部门群(保持成员关系=群关系一致)
|
||||
// checkDelete=false:业务流程跳过 owner_id/important 校验
|
||||
if ($this->dialog_id > 0) {
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
$dialog?->exitGroup($oldUser->userid, 'remove', false, true);
|
||||
}
|
||||
}
|
||||
if ($newUser) {
|
||||
$newUser->department = array_diff($newUser->department, [$this->id]);
|
||||
@ -126,6 +224,123 @@ class UserDepartment extends AbstractModel
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 任命部门管理员
|
||||
* - 部门管理员自动加入 users.department(成为部门成员,与负责人对齐)
|
||||
* - 部门管理员自动加入部门群 + 设 role=2
|
||||
* - 幂等(已是部门管理员不报错)
|
||||
*
|
||||
* @param int $userid
|
||||
* @return void
|
||||
* @throws ApiException
|
||||
*/
|
||||
public function addDeputy($userid)
|
||||
{
|
||||
if ($userid <= 0) {
|
||||
throw new ApiException('请选择有效的成员');
|
||||
}
|
||||
$user = User::whereUserid($userid)->first();
|
||||
if (!$user) {
|
||||
throw new ApiException('该用户不存在');
|
||||
}
|
||||
if ((int)$this->owner_userid === (int)$userid) {
|
||||
throw new ApiException('不能将部门负责人任命为部门管理员');
|
||||
}
|
||||
|
||||
AbstractModel::transaction(function () use ($userid, $user) {
|
||||
// 写部门管理员表(unique key 自动幂等)
|
||||
\DB::table('user_department_owners')->insertOrIgnore([
|
||||
'department_id' => $this->id,
|
||||
'userid' => $userid,
|
||||
]);
|
||||
|
||||
// 加入 users.department(成为部门成员,与负责人对齐)
|
||||
$userDeptIds = $user->department; // accessor 返回数组
|
||||
if (!in_array($this->id, $userDeptIds)) {
|
||||
$userDeptIds = array_merge($userDeptIds, [$this->id]);
|
||||
$user->department = "," . implode(",", $userDeptIds) . ",";
|
||||
$user->save();
|
||||
}
|
||||
|
||||
// 加部门管理员入部门群 + 设 role=2 + important=true
|
||||
if ($this->dialog_id > 0) {
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
if ($dialog) {
|
||||
// joinGroup($userid, $inviter, $important=null, $pushMsg=true)
|
||||
// important=true:部门管理员成员关系不可被普通群操作打散
|
||||
$dialog->joinGroup($userid, 0, true, true);
|
||||
WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', $userid)
|
||||
->update(['role' => 2]);
|
||||
$dialog->pushMsg('groupUpdate', [
|
||||
'id' => $dialog->id,
|
||||
'deputy_ids' => $dialog->fresh()->deputy_ids,
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 罢免部门管理员
|
||||
* - 删部门管理员表记录
|
||||
* - 从 users.department 移除该部门 ID(与负责人"离开部门"对齐)
|
||||
* - 退出部门群(成员关系=群关系一致)
|
||||
* - 幂等
|
||||
*
|
||||
* @param int $userid
|
||||
* @return void
|
||||
*/
|
||||
public function delDeputy($userid)
|
||||
{
|
||||
if ($userid <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 防御:当前部门负责人不能被罢免(saveDepartment 应已清理残留,此处兜底)
|
||||
// 仅清理 user_department_owners 中的悬挂记录,绝不联动移除其部门成员关系/部门群成员
|
||||
if ((int)$this->owner_userid === (int)$userid) {
|
||||
\DB::table('user_department_owners')
|
||||
->where('department_id', $this->id)
|
||||
->where('userid', $userid)
|
||||
->delete();
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractModel::transaction(function () use ($userid) {
|
||||
$deleted = \DB::table('user_department_owners')
|
||||
->where('department_id', $this->id)
|
||||
->where('userid', $userid)
|
||||
->delete();
|
||||
|
||||
if ($deleted > 0) {
|
||||
// 从 users.department 移除该部门 ID
|
||||
$user = User::whereUserid($userid)->first();
|
||||
if ($user) {
|
||||
$userDeptIds = $user->department;
|
||||
if (in_array($this->id, $userDeptIds)) {
|
||||
$userDeptIds = array_diff($userDeptIds, [$this->id]);
|
||||
$user->department = "," . implode(",", $userDeptIds) . ",";
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
// 退出部门群(exitGroup 会清除 dialog_users 记录,role 随之消失)
|
||||
if ($this->dialog_id > 0) {
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
if ($dialog) {
|
||||
// checkDelete=false:业务流程跳过 owner_id/important 校验
|
||||
$dialog->exitGroup($userid, 'remove', false, true);
|
||||
$dialog->pushMsg('groupUpdate', [
|
||||
'id' => $dialog->id,
|
||||
'deputy_ids' => $dialog->fresh()->deputy_ids,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
* @return void
|
||||
@ -148,6 +363,8 @@ class UserDepartment extends AbstractModel
|
||||
// 解散群组
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
$dialog?->deleteDialog();
|
||||
// 清理部门管理员记录(防悬挂)
|
||||
\DB::table('user_department_owners')->where('department_id', $this->id)->delete();
|
||||
//
|
||||
$this->delete();
|
||||
}
|
||||
@ -160,6 +377,7 @@ class UserDepartment extends AbstractModel
|
||||
*/
|
||||
public static function transfer($originalUserid, $newUserid)
|
||||
{
|
||||
// 部门负责人转让(保持现有逻辑)
|
||||
self::whereOwnerUserid($originalUserid)->chunkById(100, function ($list) use ($originalUserid, $newUserid) {
|
||||
/** @var self $item */
|
||||
foreach ($list as $item) {
|
||||
@ -168,6 +386,11 @@ class UserDepartment extends AbstractModel
|
||||
]);
|
||||
}
|
||||
});
|
||||
// 部门管理员离职清理(新增):直接删除离职用户的所有部门管理员记录
|
||||
// 不需要清群 role —— UserTransfer::exitDialog 会把人踢出所有群,role 随成员关系一起消失
|
||||
\DB::table('user_department_owners')
|
||||
->where('userid', $originalUserid)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,6 +413,93 @@ class UserDepartment extends AbstractModel
|
||||
return array_unique($subIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户可切换负责人视角的部门(正负责人 + 部门管理员)
|
||||
* @param int $userid
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public static function getManagedDepartments($userid)
|
||||
{
|
||||
$userid = intval($userid);
|
||||
if ($userid <= 0) {
|
||||
return collect();
|
||||
}
|
||||
$deputyDepartmentIds = \DB::table('user_department_owners')
|
||||
->where('userid', $userid)
|
||||
->pluck('department_id')
|
||||
->map(fn($v) => intval($v))
|
||||
->toArray();
|
||||
|
||||
return self::select(['id', 'name', 'parent_id', 'owner_userid'])
|
||||
->where(function ($query) use ($userid, $deputyDepartmentIds) {
|
||||
$query->where('owner_userid', $userid);
|
||||
if ($deputyDepartmentIds) {
|
||||
$query->orWhereIn('id', $deputyDepartmentIds);
|
||||
}
|
||||
})
|
||||
->orderBy('id')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户选择的负责人视角部门范围(含所有下级部门)
|
||||
* @param int $userid
|
||||
* @param array|string|null $selectedIds all/空表示全部可管理部门
|
||||
* @return array
|
||||
*/
|
||||
public static function getManagedDepartmentScopeIds($userid, $selectedIds = null): array
|
||||
{
|
||||
$managedIds = self::getManagedDepartments($userid)->pluck('id')->map(fn($v) => intval($v))->toArray();
|
||||
if (empty($managedIds)) {
|
||||
return [];
|
||||
}
|
||||
if ($selectedIds === 'all' || $selectedIds === null || $selectedIds === '' || $selectedIds === []) {
|
||||
$selected = $managedIds;
|
||||
} else {
|
||||
if (!is_array($selectedIds)) {
|
||||
$selectedIds = explode(',', (string)$selectedIds);
|
||||
}
|
||||
$selected = array_values(array_intersect(
|
||||
array_map('intval', $selectedIds),
|
||||
$managedIds
|
||||
));
|
||||
}
|
||||
if (empty($selected)) {
|
||||
return [];
|
||||
}
|
||||
$scopeIds = [];
|
||||
foreach ($selected as $departmentId) {
|
||||
$scopeIds[] = $departmentId;
|
||||
$scopeIds = array_merge($scopeIds, self::getAllSubDepartmentIds($departmentId));
|
||||
}
|
||||
return array_values(array_unique(array_map('intval', $scopeIds)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取负责人视角可管理的成员 userid
|
||||
* @param int $userid
|
||||
* @param array|string|null $selectedIds
|
||||
* @return array
|
||||
*/
|
||||
public static function getManagedMemberUserids($userid, $selectedIds = null): array
|
||||
{
|
||||
$departmentIds = self::getManagedDepartmentScopeIds($userid, $selectedIds);
|
||||
if (empty($departmentIds)) {
|
||||
return [];
|
||||
}
|
||||
return User::select(['userid'])
|
||||
->where(function ($query) use ($departmentIds) {
|
||||
foreach ($departmentIds as $departmentId) {
|
||||
$query->orWhere('department', 'like', "%,{$departmentId},%");
|
||||
}
|
||||
})
|
||||
->pluck('userid')
|
||||
->map(fn($v) => intval($v))
|
||||
->unique()
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门基本信息(缓存时间1小时)
|
||||
* @param int|array $ids
|
||||
@ -232,4 +542,77 @@ class UserDepartment extends AbstractModel
|
||||
return is_array($ids) ? $result : $result->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门负责人视角上下文(只读)。
|
||||
* $defaultAll=true 用于项目内只读辅助接口兜底:前端漏传部门选择时按全部可管理部门判断。
|
||||
*/
|
||||
public static function ownerViewContext(User $user, bool $defaultAll = false): array
|
||||
{
|
||||
$ids = Request::input('department_owner_ids', Request::input('department_ids'));
|
||||
if (($ids === null || $ids === '') && $defaultAll) {
|
||||
$ids = 'all';
|
||||
}
|
||||
$empty = [
|
||||
'enabled' => false,
|
||||
'member_userids' => [],
|
||||
'project_ids' => [],
|
||||
'project_id_map' => [],
|
||||
'own_project_ids' => [],
|
||||
'own_project_id_map' => [],
|
||||
];
|
||||
if ($ids === null || $ids === '' || Base::settingFind('system', 'department_owner_project_view', 'close') !== 'open') {
|
||||
return $empty;
|
||||
}
|
||||
$memberUserids = self::getManagedMemberUserids($user->userid, $ids);
|
||||
if (empty($memberUserids)) {
|
||||
return $empty;
|
||||
}
|
||||
// 项目可单独关闭"部门负责人视角可见",关闭后对负责人隐藏(含项目和任务群聊)
|
||||
$projectIds = ProjectUser::whereIn('project_users.userid', $memberUserids)
|
||||
->join('projects', 'projects.id', '=', 'project_users.project_id')
|
||||
->whereNull('projects.deleted_at')
|
||||
->where(function ($query) {
|
||||
$query->where('projects.department_owner_view', '<>', 'close')
|
||||
->orWhereNull('projects.department_owner_view');
|
||||
})
|
||||
->distinct()
|
||||
->pluck('projects.id')
|
||||
->map(fn($v) => intval($v))
|
||||
->values()
|
||||
->toArray();
|
||||
$ownProjectIds = ProjectUser::whereUserid($user->userid)
|
||||
->pluck('project_id')
|
||||
->map(fn($v) => intval($v))
|
||||
->unique()
|
||||
->values()
|
||||
->toArray();
|
||||
return [
|
||||
'enabled' => !empty($projectIds),
|
||||
'member_userids' => $memberUserids,
|
||||
'project_ids' => $projectIds,
|
||||
'project_id_map' => array_fill_keys($projectIds, true),
|
||||
'own_project_ids' => $ownProjectIds,
|
||||
'own_project_id_map' => array_fill_keys($ownProjectIds, true),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断项目是否属于部门只读范围(非本人项目)
|
||||
*/
|
||||
public static function isDepartmentReadonlyProject(array $context, int $projectId): bool
|
||||
{
|
||||
return !empty($context['enabled'])
|
||||
&& isset($context['project_id_map'][$projectId])
|
||||
&& !isset($context['own_project_id_map'][$projectId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为项目数据附加部门只读标记
|
||||
*/
|
||||
public static function appendDepartmentReadonlyProject(array $project, array $context): array
|
||||
{
|
||||
$project['department_readonly'] = self::isDepartmentReadonlyProject($context, intval($project['id']));
|
||||
return $project;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -90,9 +90,15 @@ class UserTransfer extends AbstractModel
|
||||
$dialog->owner_id = $this->new_userid;
|
||||
if ($dialog->save()) {
|
||||
$dialog->joinGroup($this->new_userid, 0);
|
||||
// 同步 role=1:保证 deputy_ids 与 owner_id 一致
|
||||
// 若 new_userid 之前是群管理员(role=2),升为群主后必须从 deputy 列表移出
|
||||
WebSocketDialogUser::where('dialog_id', $dialog->id)
|
||||
->where('userid', $this->new_userid)
|
||||
->update(['role' => 1]);
|
||||
$dialog->pushMsg("groupUpdate", [
|
||||
'id' => $dialog->id,
|
||||
'owner_id' => $dialog->owner_id,
|
||||
'deputy_ids' => $dialog->fresh()->deputy_ids,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +62,8 @@ class WebSocketDialog extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $appends = ['deputy_ids'];
|
||||
|
||||
/**
|
||||
* 头像地址
|
||||
* @param $value
|
||||
@ -260,6 +262,15 @@ class WebSocketDialog extends AbstractModel
|
||||
$data[$field] = $data[$field] ?? null;
|
||||
}
|
||||
}
|
||||
// DB::table 列表/search/beyond 渠道进入的是 stdClass,不会触发 Eloquent $appends。
|
||||
// 这里统一补齐 deputy_ids,保证群管理员入口和标识在所有会话来源中一致。
|
||||
if (($data['type'] ?? null) === 'group' && !array_key_exists('deputy_ids', $data)) {
|
||||
$data['deputy_ids'] = WebSocketDialogUser::whereDialogId($data['id'])
|
||||
->where('role', 2)
|
||||
->pluck('userid')
|
||||
->map(fn($v) => (int)$v)
|
||||
->toArray();
|
||||
}
|
||||
$data['avatar'] = Base::fillUrl($data['avatar']);
|
||||
|
||||
// 会员必要字段
|
||||
@ -457,11 +468,12 @@ class WebSocketDialog extends AbstractModel
|
||||
* @param int|array $userid 加入的会员ID或会员ID组
|
||||
* @param int $inviter 邀请人
|
||||
* @param bool|null $important 重要人员(null不修改、bool修改)
|
||||
* @param bool $pushMsg 是否推送消息
|
||||
* @return bool
|
||||
*/
|
||||
public function joinGroup($userid, $inviter, $important = null)
|
||||
public function joinGroup($userid, $inviter, $important = null, $pushMsg = true)
|
||||
{
|
||||
AbstractModel::transaction(function () use ($important, $inviter, $userid) {
|
||||
AbstractModel::transaction(function () use ($important, $inviter, $userid, $pushMsg) {
|
||||
foreach (is_array($userid) ? $userid : [$userid] as $value) {
|
||||
if ($value > 0) {
|
||||
$updateData = [
|
||||
@ -479,7 +491,7 @@ class WebSocketDialog extends AbstractModel
|
||||
'bot' => User::isBot($value) ? 1 : 0
|
||||
]);
|
||||
}, $isInsert);
|
||||
if ($isInsert) {
|
||||
if ($isInsert && $pushMsg) {
|
||||
WebSocketDialogMsg::sendMsg(null, $this->id, 'notice', [
|
||||
'notice' => User::userid2nickname($value) . " 已加入群组"
|
||||
], $inviter, true, true);
|
||||
@ -487,9 +499,11 @@ class WebSocketDialog extends AbstractModel
|
||||
}
|
||||
}
|
||||
});
|
||||
$data = WebSocketDialog::generatePeople($this->id);
|
||||
$data['id'] = $this->id;
|
||||
$this->pushMsg("groupUpdate", $data);
|
||||
if ($pushMsg) {
|
||||
$data = WebSocketDialog::generatePeople($this->id);
|
||||
$data['id'] = $this->id;
|
||||
$this->pushMsg("groupUpdate", $data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -515,11 +529,40 @@ class WebSocketDialog extends AbstractModel
|
||||
foreach ($list as $item) {
|
||||
if ($checkDelete) {
|
||||
if ($type === 'remove') {
|
||||
// 移出时:如果是全员群仅允许管理员操作,其他群仅群主或邀请人可以操作
|
||||
// 移出时:如果是全员群仅允许管理员操作,其他群主/群管理员/邀请人可以操作
|
||||
if ($this->group_type === 'all') {
|
||||
User::auth("admin");
|
||||
} elseif (!in_array(User::userid(), [$this->owner_id, $item->inviter])) {
|
||||
throw new ApiException('只有群主或邀请人可以移出成员');
|
||||
} else {
|
||||
$actor = User::userid();
|
||||
// 未认证时拒绝
|
||||
if ($actor <= 0) {
|
||||
throw new ApiException('只有群主或邀请人可以移出成员');
|
||||
}
|
||||
|
||||
// 目标是群主或群管理员时的保护
|
||||
$targetIsPrimaryOwner = $this->isPrimaryOwner($item->userid);
|
||||
$targetIsDeputyOwner = $this->isDeputyOwner($item->userid);
|
||||
|
||||
if ($targetIsPrimaryOwner || $targetIsDeputyOwner) {
|
||||
// 普通邀请人不能移出群主或群管理员
|
||||
$actorIsPrimaryOwner = $this->isPrimaryOwner($actor);
|
||||
$actorIsDeputyOwner = $this->isDeputyOwner($actor);
|
||||
|
||||
if (!$actorIsPrimaryOwner && !$actorIsDeputyOwner) {
|
||||
throw new ApiException('普通成员不能移出群主或群管理员');
|
||||
}
|
||||
|
||||
// 群管理员不能移出群主或其他群管理员
|
||||
if ($actorIsDeputyOwner && !$actorIsPrimaryOwner) {
|
||||
throw new ApiException('群管理员不能移出群主或其他群管理员');
|
||||
}
|
||||
}
|
||||
|
||||
// 普通成员:群主、群管理员、邀请人可移出
|
||||
$allowedActor = $this->isOwner($actor) || $actor === (int)$item->inviter;
|
||||
if (!$allowedActor) {
|
||||
throw new ApiException('只有群主、群管理员或邀请人可以移出成员');
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($item->userid == $this->owner_id) {
|
||||
@ -547,9 +590,11 @@ class WebSocketDialog extends AbstractModel
|
||||
});
|
||||
});
|
||||
//
|
||||
$data = WebSocketDialog::generatePeople($this->id);
|
||||
$data['id'] = $this->id;
|
||||
$this->pushMsg("groupUpdate", $data);
|
||||
if ($pushMsg) {
|
||||
$data = WebSocketDialog::generatePeople($this->id);
|
||||
$data['id'] = $this->id;
|
||||
$this->pushMsg("groupUpdate", $data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -635,6 +680,53 @@ class WebSocketDialog extends AbstractModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否群主(与 owner_id 一致)
|
||||
*/
|
||||
public function isPrimaryOwner($userid): bool
|
||||
{
|
||||
return $userid > 0 && (int)$this->owner_id === (int)$userid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否群管理员(仅 web_socket_dialog_users.role=2)
|
||||
*/
|
||||
public function isDeputyOwner($userid): bool
|
||||
{
|
||||
if ($userid <= 0) {
|
||||
return false;
|
||||
}
|
||||
return WebSocketDialogUser::where('dialog_id', $this->id)
|
||||
->where('userid', $userid)
|
||||
->where('role', 2)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否群主(含群管理员)
|
||||
*/
|
||||
public function isOwner($userid): bool
|
||||
{
|
||||
return $this->isPrimaryOwner($userid) || $this->isDeputyOwner($userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 群管理员 userid 列表
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDeputyIdsAttribute(): array
|
||||
{
|
||||
if (!$this->id) {
|
||||
return [];
|
||||
}
|
||||
return WebSocketDialogUser::where('dialog_id', $this->id)
|
||||
->where('role', 2)
|
||||
->pluck('userid')
|
||||
->map(fn($v) => (int)$v)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查禁言
|
||||
* @param $userid
|
||||
@ -820,6 +912,13 @@ class WebSocketDialog extends AbstractModel
|
||||
if ($projectId > 0 && ProjectUser::whereProjectId($projectId)->whereUserid($userid)->exists()) {
|
||||
return $dialog;
|
||||
}
|
||||
// 部门负责人只读视角:项目/任务群按项目级共享放行(任务数据另按可见性校验,与普通成员一致)
|
||||
if ($projectId > 0 && $checkOwner === false) {
|
||||
$departmentView = UserDepartment::ownerViewContext(User::auth(), true);
|
||||
if (UserDepartment::isDepartmentReadonlyProject($departmentView, $projectId)) {
|
||||
return $dialog;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'okr':
|
||||
@ -857,6 +956,7 @@ class WebSocketDialog extends AbstractModel
|
||||
WebSocketDialogUser::createInstance([
|
||||
'dialog_id' => $dialog->id,
|
||||
'userid' => $value,
|
||||
'role' => ($owner_id > 0 && (int)$value === (int)$owner_id) ? 1 : 0,
|
||||
'bot' => User::isBot($value) ? 1 : 0,
|
||||
'important' => !in_array($group_type, ['user', 'all']),
|
||||
'last_at' => in_array($group_type, ['user', 'department', 'all']) ? Carbon::now() : null,
|
||||
|
||||
@ -233,6 +233,10 @@ class AI
|
||||
$authParams['agency'] = $agency;
|
||||
}
|
||||
|
||||
// 从模型名末尾剥离思考标记,支持以下写法:
|
||||
// 模型名 think / 模型名-thinking / 模型名_reasoning (空格、- 、_ 作分隔)
|
||||
// 模型名(think) / 模型名 ( reasoning ) (括号包裹)
|
||||
// 关键词三选一:think | thinking | reasoning
|
||||
$thinkPatterns = [
|
||||
"/^(.+?)(\s+|\s*[_-]\s*)(think|thinking|reasoning)\s*$/",
|
||||
"/^(.+?)\s*\(\s*(think|thinking|reasoning)\s*\)\s*$/"
|
||||
@ -243,6 +247,7 @@ class AI
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 命中后把关键词剥掉,只保留前面的真实模型名
|
||||
if ($thinkMatch && !empty($thinkMatch[1])) {
|
||||
$authParams['model_name'] = $thinkMatch[1];
|
||||
}
|
||||
|
||||
@ -113,7 +113,9 @@ class ProjectTaskObserver extends AbstractObserver
|
||||
return ProjectUser::whereProjectId($projectTask->project_id)->pluck('userid')->toArray();
|
||||
}
|
||||
if (in_array('projectOwnerUser', $dataType)) {
|
||||
return ProjectUser::whereProjectId($projectTask->project_id)->where('owner', 1)->pluck('userid')->toArray();
|
||||
return ProjectUser::whereProjectId($projectTask->project_id)
|
||||
->whereIn('owner', [ProjectUser::OWNER_PRIMARY, ProjectUser::OWNER_DEPUTY])
|
||||
->pluck('userid')->toArray();
|
||||
}
|
||||
$array = [];
|
||||
if (in_array('task', $dataType)) {
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddRoleToWebSocketDialogUsers extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('web_socket_dialog_users', 'role')) {
|
||||
$table->tinyInteger('role')->default(0)->after('userid')
|
||||
->comment('0=普通成员 1=群主 2=群管理员');
|
||||
$table->index(['dialog_id', 'role'], 'idx_dialog_role');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('web_socket_dialog_users', 'role')) {
|
||||
$table->dropIndex('idx_dialog_role');
|
||||
$table->dropColumn('role');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BackfillDialogOwnerRole extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$prefix = DB::getTablePrefix();
|
||||
// 把每个群里 userid = web_socket_dialogs.owner_id 的成员记录设为 role=1(主群主)
|
||||
// 幂等:仅当 role=0 时才更新
|
||||
DB::statement("
|
||||
UPDATE {$prefix}web_socket_dialog_users du
|
||||
INNER JOIN {$prefix}web_socket_dialogs d ON d.id = du.dialog_id
|
||||
SET du.role = 1
|
||||
WHERE d.owner_id > 0
|
||||
AND du.userid = d.owner_id
|
||||
AND du.role = 0
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
$prefix = DB::getTablePrefix();
|
||||
// 回滚:把 role=1 的记录全部回到 role=0
|
||||
DB::statement("UPDATE {$prefix}web_socket_dialog_users SET role = 0 WHERE role = 1");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUserDepartmentOwnersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (!Schema::hasTable('user_department_owners')) {
|
||||
Schema::create('user_department_owners', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedBigInteger('department_id')->comment('部门ID');
|
||||
$table->unsignedBigInteger('userid')->comment('部门管理员 userid');
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->unique(['department_id', 'userid'], 'uniq_dept_user');
|
||||
$table->index('userid', 'idx_userid');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
if (Schema::hasTable('user_department_owners')) {
|
||||
Schema::dropIfExists('user_department_owners');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BackfillProjectDialogPrimaryOwner extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* 修复历史项目群聊未登记群主的问题:
|
||||
* - 早期 Project::addProject 调 createGroup 时未传 owner_id 第 4 参
|
||||
* (在 commit 3a9001e09 才补上),导致老项目群 dialogs.owner_id = 0
|
||||
* - 这些群也因此被 2026_04_30_000002_backfill_dialog_owner_role 跳过
|
||||
* (那条迁移要求 owner_id > 0),主负责人那条 dialog_users.role 仍为 0
|
||||
*
|
||||
* 本迁移仅处理 group_type = 'project' 且未软删的项目:
|
||||
* (a) dialogs.owner_id = 0 → 按 project_users.owner=1 回填
|
||||
* (b) 同一批群里主负责人那条 dialog_users.role = 0 → 设为 1
|
||||
*
|
||||
* 全部带幂等条件,可重跑。
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$prefix = DB::getTablePrefix();
|
||||
|
||||
// (a) 回填 dialogs.owner_id
|
||||
DB::statement("
|
||||
UPDATE {$prefix}web_socket_dialogs d
|
||||
INNER JOIN {$prefix}projects p ON p.dialog_id = d.id
|
||||
INNER JOIN {$prefix}project_users pu ON pu.project_id = p.id AND pu.owner = 1
|
||||
SET d.owner_id = pu.userid
|
||||
WHERE d.owner_id = 0
|
||||
AND d.group_type = 'project'
|
||||
AND p.deleted_at IS NULL
|
||||
");
|
||||
|
||||
// (b) 把这些项目群里主负责人那条 dialog_users.role 设为 1
|
||||
// 不依赖 (a) 的结果,直接按 project_users.owner=1 反查,幂等条件 du.role=0
|
||||
DB::statement("
|
||||
UPDATE {$prefix}web_socket_dialog_users du
|
||||
INNER JOIN {$prefix}projects p ON p.dialog_id = du.dialog_id
|
||||
INNER JOIN {$prefix}project_users pu
|
||||
ON pu.project_id = p.id
|
||||
AND pu.userid = du.userid
|
||||
AND pu.owner = 1
|
||||
SET du.role = 1
|
||||
WHERE du.role = 0
|
||||
AND p.deleted_at IS NULL
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* 数据回填类迁移不提供精确回滚——回滚会丢失原本就正确的数据。
|
||||
* 如需重置,请手动操作。
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BackfillDialogRoleConsistency extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* 兜底修复 role/owner_id 一致性:
|
||||
* - 部门群 owner_id 与 user_departments.owner_userid 对齐
|
||||
* - owner_id > 0 的群确保 owner 成员存在且 role=1
|
||||
* - 同一群中非 owner 的 role=1 降为普通成员(不影响 role=2 管理员)
|
||||
* - 历史 owner_id=0 的普通用户群按最早的非机器人成员回填群主
|
||||
* - 清理部门负责人残留的 user_department_owners 记录
|
||||
*
|
||||
* 全部语句带幂等条件,可重复运行。
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$prefix = DB::getTablePrefix();
|
||||
|
||||
// 1) 部门群 owner_id 以 user_departments.owner_userid 为准
|
||||
DB::statement("\n UPDATE {$prefix}web_socket_dialogs d\n INNER JOIN {$prefix}user_departments ud ON ud.dialog_id = d.id\n SET d.owner_id = ud.owner_userid\n WHERE d.type = 'group'\n AND d.group_type = 'department'\n AND d.deleted_at IS NULL\n AND ud.owner_userid > 0\n AND d.owner_id != ud.owner_userid\n ");
|
||||
|
||||
// 2) 历史普通用户群 owner_id=0:按最早加入的非机器人成员回填群主
|
||||
DB::statement("\n UPDATE {$prefix}web_socket_dialogs d\n INNER JOIN (\n SELECT du.dialog_id, MIN(du.id) AS min_id\n FROM {$prefix}web_socket_dialog_users du\n WHERE du.userid > 0 AND du.bot = 0\n GROUP BY du.dialog_id\n ) first_du ON first_du.dialog_id = d.id\n INNER JOIN {$prefix}web_socket_dialog_users owner_du ON owner_du.id = first_du.min_id\n SET d.owner_id = owner_du.userid\n WHERE d.type = 'group'\n AND d.group_type = 'user'\n AND d.deleted_at IS NULL\n AND d.owner_id = 0\n ");
|
||||
|
||||
// 3) owner_id > 0 但 owner 不在群成员表时,补一条成员记录(仅补真实存在的用户)
|
||||
DB::statement("\n INSERT INTO {$prefix}web_socket_dialog_users\n (dialog_id, userid, role, bot, important, last_at, created_at, updated_at)\n SELECT\n d.id,\n d.owner_id,\n 1,\n COALESCE(u.bot, 0),\n CASE WHEN d.group_type IN ('user', 'all') THEN 0 ELSE 1 END,\n CASE WHEN d.group_type IN ('user', 'department', 'all') THEN NOW(3) ELSE NULL END,\n NOW(3),\n NOW(3)\n FROM {$prefix}web_socket_dialogs d\n INNER JOIN {$prefix}users u ON u.userid = d.owner_id\n LEFT JOIN {$prefix}web_socket_dialog_users du\n ON du.dialog_id = d.id AND du.userid = d.owner_id\n WHERE d.type = 'group'\n AND d.deleted_at IS NULL\n AND d.owner_id > 0\n AND du.id IS NULL\n ");
|
||||
|
||||
// 4) owner 成员设为 role=1;业务群 owner 同时保持 important=1
|
||||
DB::statement("\n UPDATE {$prefix}web_socket_dialog_users du\n INNER JOIN {$prefix}web_socket_dialogs d ON d.id = du.dialog_id\n SET du.role = 1,\n du.important = CASE WHEN d.group_type IN ('user', 'all') THEN du.important ELSE 1 END\n WHERE d.type = 'group'\n AND d.deleted_at IS NULL\n AND d.owner_id > 0\n AND du.userid = d.owner_id\n AND (du.role != 1 OR (d.group_type NOT IN ('user', 'all') AND du.important != 1))\n ");
|
||||
|
||||
// 5) 同一群里非 owner 的 role=1 降为普通成员,避免多个主群主
|
||||
DB::statement("\n UPDATE {$prefix}web_socket_dialog_users du\n INNER JOIN {$prefix}web_socket_dialogs d ON d.id = du.dialog_id\n SET du.role = 0\n WHERE d.type = 'group'\n AND d.deleted_at IS NULL\n AND d.owner_id > 0\n AND du.role = 1\n AND du.userid != d.owner_id\n ");
|
||||
|
||||
// 6) 部门负责人不能同时残留在部门管理员表
|
||||
DB::statement("\n DELETE udo FROM {$prefix}user_department_owners udo\n INNER JOIN {$prefix}user_departments ud ON ud.id = udo.department_id\n WHERE ud.owner_userid = udo.userid\n ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* 数据修复类迁移不提供精确回滚,避免破坏已校准的数据。
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddUseCountToProjectTaskTemplates extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('project_task_templates', function (Blueprint $table) {
|
||||
$table->unsignedInteger('use_count')->default(0)->after('is_default')->comment('累计使用次数');
|
||||
$table->timestamp('last_used_at')->nullable()->after('use_count')->comment('最近一次使用时间');
|
||||
$table->index(['use_count', 'last_used_at'], 'idx_template_usage');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('project_task_templates', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_template_usage');
|
||||
$table->dropColumn(['use_count', 'last_used_at']);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddTaskTemplateShareToProjectsTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('projects', 'task_template_share')) {
|
||||
$table->string('task_template_share', 20)->default('open')->after('ai_auto_analyze')->comment('共享模板开关');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('projects', 'task_template_share')) {
|
||||
$table->dropColumn('task_template_share');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddDepartmentOwnerViewToProjectsTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('projects', 'department_owner_view')) {
|
||||
$table->string('department_owner_view', 20)->default('open')->after('task_template_share')->comment('部门负责人视角可见开关');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('projects', 'department_owner_view')) {
|
||||
$table->dropColumn('department_owner_view');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
334
electron/build.js
vendored
334
electron/build.js
vendored
@ -6,13 +6,14 @@ const child_process = require('child_process');
|
||||
const ora = require('ora');
|
||||
const yauzl = require('yauzl');
|
||||
const axios = require('axios');
|
||||
const FormData =require('form-data');
|
||||
const tar = require('tar');
|
||||
const utils = require('./lib/utils');
|
||||
const r2 = require('./lib/r2');
|
||||
const { buildReleaseIndex } = require('./lib/release-index');
|
||||
const config = require('../package.json')
|
||||
const env = require('dotenv').config({ path: './.env' })
|
||||
const argv = process.argv;
|
||||
const {BUILD_FRONTEND, APPLEID, APPLEIDPASS, GITHUB_TOKEN, GITHUB_REPOSITORY, UPLOAD_TOKEN, UPLOAD_URL} = process.env;
|
||||
const {BUILD_FRONTEND, APPLEID, APPLEIDPASS, GITHUB_TOKEN, GITHUB_REPOSITORY} = process.env;
|
||||
|
||||
const electronDir = path.resolve(__dirname, "public");
|
||||
const nativeCachePath = path.resolve(__dirname, ".native");
|
||||
@ -25,6 +26,9 @@ 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`;
|
||||
|
||||
/**
|
||||
* 检测并下载更新器
|
||||
*/
|
||||
@ -308,193 +312,23 @@ function changeLog() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装 axios 自动重试
|
||||
* @param data // {axios: object{}, onRetry: function, retryNumber: number}
|
||||
* @returns {Promise<unknown>}
|
||||
* 上传单个文件到 R2 的 draft/<version>/ 目录(带进度/spinner)
|
||||
*/
|
||||
function axiosAutoTry(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios(data.axios).then(result => {
|
||||
resolve(result)
|
||||
}).catch(error => {
|
||||
if (typeof data.retryNumber == 'number' && data.retryNumber > 0) {
|
||||
data.retryNumber--;
|
||||
if (typeof data.onRetry === "function") {
|
||||
data.onRetry(error)
|
||||
}
|
||||
if (error.code == 'ECONNABORTED' || error.code == 'ECONNRESET') {
|
||||
// 中止,超时
|
||||
return resolve(axiosAutoTry(data))
|
||||
} else {
|
||||
if (error.response && error.response.status == 407) {
|
||||
// 代理407
|
||||
return setTimeout(v => {
|
||||
resolve(axiosAutoTry(data))
|
||||
}, 500 + Math.random() * 500)
|
||||
} else if (error.response && error.response.status == 503) {
|
||||
// 服务器异常
|
||||
return setTimeout(v => {
|
||||
resolve(axiosAutoTry(data))
|
||||
}, 1000 + Math.random() * 500)
|
||||
} else if (error.response && error.response.status == 429) {
|
||||
// 并发超过限制
|
||||
return setTimeout(v => {
|
||||
resolve(axiosAutoTry(data))
|
||||
}, 1000 + Math.random() * 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 官网发布器
|
||||
*/
|
||||
class WebsitePublisher {
|
||||
constructor({baseUrl, token, version}) {
|
||||
this.baseUrl = baseUrl
|
||||
this.token = token
|
||||
this.version = version
|
||||
async function uploadDraftFile(client, localFile, version) {
|
||||
const filename = path.basename(localFile);
|
||||
const key = `draft/${version}/${filename}`;
|
||||
const startTime = Date.now();
|
||||
const spinner = ora(`Upload [0%] ${filename}`).start();
|
||||
try {
|
||||
await r2.uploadFile(client, localFile, key, (loaded, total) => {
|
||||
const pct = Math.min(99, Math.round((loaded / total) * 100)) + '%';
|
||||
spinner.text = `Upload [${pct}] ${filename}`;
|
||||
});
|
||||
} catch (error) {
|
||||
spinner.fail(`Upload [fail] ${filename} (${elapsedSeconds(startTime)}): ${error.message || error}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传单个文件
|
||||
* @param localFile 本地文件路径
|
||||
* @param options { platform, arch } 可选,有则为安装包
|
||||
*/
|
||||
async uploadPackage(localFile, options = {}) {
|
||||
const filename = path.basename(localFile)
|
||||
let spinner = ora(`Upload [0%] ${filename}`).start()
|
||||
const formData = new FormData()
|
||||
formData.append("version", this.version)
|
||||
if (options.platform) {
|
||||
formData.append("platform", options.platform)
|
||||
if (options.arch) {
|
||||
formData.append("arch", options.arch)
|
||||
}
|
||||
}
|
||||
formData.append("file", fs.createReadStream(localFile))
|
||||
const {status, data} = await axiosAutoTry({
|
||||
axios: {
|
||||
method: 'post',
|
||||
url: `${this.baseUrl}/api/upload/package`,
|
||||
data: formData,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.token}`,
|
||||
'Content-Type': 'multipart/form-data;boundary=' + formData.getBoundary(),
|
||||
},
|
||||
onUploadProgress: progress => {
|
||||
const complete = Math.min(99, Math.round(progress.loaded / progress.total * 100 | 0)) + '%'
|
||||
spinner.text = `Upload [${complete}] ${filename}`
|
||||
},
|
||||
},
|
||||
onRetry: (err) => {
|
||||
const reason = err?.response?.status || err?.code || err?.message || ''
|
||||
spinner.warn(`Upload [retry] ${filename}${reason ? ': ' + reason : ''}`)
|
||||
spinner = ora(`Upload [0%] ${filename}`).start()
|
||||
},
|
||||
retryNumber: 3
|
||||
})
|
||||
if (status !== 200 || !utils.isJson(data) || !data.success) {
|
||||
const reason = data?.message || `status ${status}`
|
||||
spinner.fail(`Upload [fail] ${filename}: ${reason}`)
|
||||
throw new Error(`Upload failed: ${filename}: ${reason}`)
|
||||
}
|
||||
spinner.succeed(`Upload [100%] ${filename}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传 changelog
|
||||
*/
|
||||
async uploadChangelog(content) {
|
||||
const spinner = ora('Uploading changelog...').start()
|
||||
const {status, data} = await axiosAutoTry({
|
||||
axios: {
|
||||
method: 'post',
|
||||
url: `${this.baseUrl}/api/upload/changelog`,
|
||||
data: { content },
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
retryNumber: 3
|
||||
})
|
||||
if (status !== 200 || !data.success) {
|
||||
spinner.fail('Changelog upload failed')
|
||||
throw new Error('Changelog upload failed')
|
||||
}
|
||||
spinner.succeed('Changelog uploaded')
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知发布完成
|
||||
*/
|
||||
async release() {
|
||||
const spinner = ora('Publishing release...').start()
|
||||
const {status, data} = await axiosAutoTry({
|
||||
axios: {
|
||||
method: 'post',
|
||||
url: `${this.baseUrl}/api/upload/release`,
|
||||
data: { version: this.version },
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
retryNumber: 3
|
||||
})
|
||||
if (status !== 200 || !data.success) {
|
||||
spinner.fail(`Release failed: ${data?.message || status}`)
|
||||
throw new Error(`Release failed: ${data?.message || status}`)
|
||||
}
|
||||
spinner.succeed('Release published')
|
||||
}
|
||||
}
|
||||
|
||||
// 安装包扩展名
|
||||
const INSTALLER_EXTS = ['.dmg', '.exe', '.msi', '.appimage', '.deb', '.rpm', '.apk']
|
||||
|
||||
/**
|
||||
* 创建 WebsitePublisher 实例(如果环境变量齐全)
|
||||
*/
|
||||
function createPublisher() {
|
||||
if (!UPLOAD_TOKEN || !UPLOAD_URL) {
|
||||
return null
|
||||
}
|
||||
return new WebsitePublisher({
|
||||
baseUrl: UPLOAD_URL.replace(/\/+$/, ''),
|
||||
token: UPLOAD_TOKEN,
|
||||
version: config.version
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件名判断是否为安装包
|
||||
*/
|
||||
function isInstaller(filename) {
|
||||
return INSTALLER_EXTS.some(ext => filename.toLowerCase().endsWith(ext))
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件名提取 arch
|
||||
*/
|
||||
function parseArchFromFilename(filename) {
|
||||
if (/-arm64[.-]/i.test(filename)) return 'arm64'
|
||||
if (/-x64[.-]/i.test(filename)) return 'x64'
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 将构建平台名映射为 API platform
|
||||
*/
|
||||
function mapPlatform(buildPlatform) {
|
||||
if (buildPlatform.includes('mac')) return 'mac'
|
||||
if (buildPlatform.includes('win')) return 'win'
|
||||
if (buildPlatform.includes('linux')) return 'linux'
|
||||
return null
|
||||
spinner.succeed(`Upload [100%] ${filename} (${elapsedSeconds(startTime)})`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -568,8 +402,8 @@ async function startBuild(data) {
|
||||
//
|
||||
if (data.id === 'app') {
|
||||
const eeuiDir = path.resolve(__dirname, "../resources/mobile");
|
||||
const eeuiRun = `docker run --rm -v ${eeuiDir}:/work -w /work kuaifan/eeui-cli:0.0.1`
|
||||
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") {
|
||||
@ -587,10 +421,19 @@ async function startBuild(data) {
|
||||
fs.writeFileSync(xcconfigFile, xcconfigResult, 'utf8')
|
||||
}
|
||||
if (['build', 'publish'].includes(argv[3])) {
|
||||
if (!fs.existsSync(path.resolve(eeuiDir, "node_modules"))) {
|
||||
child_process.execSync(`${eeuiRun} npm install`, {stdio: "inherit", cwd: "resources/mobile"});
|
||||
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"});
|
||||
}
|
||||
child_process.execSync(`${eeuiRun} eeui build --simple`, {stdio: "inherit", cwd: "resources/mobile"});
|
||||
} else {
|
||||
[
|
||||
path.resolve(publicDir, "../../platforms/ios/eeuiApp/bundlejs/eeui/public"),
|
||||
@ -657,30 +500,22 @@ async function startBuild(data) {
|
||||
fs.writeFileSync(packageFile, JSON.stringify(appConfig, null, 4), 'utf8');
|
||||
child_process.execSync(`npm run ${platform}-publish`, {stdio: "inherit", cwd: "electron"});
|
||||
}
|
||||
// generic (build or publish)
|
||||
appConfig.build.publish = data.publish
|
||||
// generic (build or publish) —— 有 R2_PUBLIC_URL 时自动更新源指向 R2 release/
|
||||
appConfig.build.publish = r2.R2_PUBLIC_URL
|
||||
? { provider: 'generic', url: `${r2.R2_PUBLIC_URL.replace(/\/+$/, '')}/release` }
|
||||
: data.publish
|
||||
appConfig.build.directories.output = `${output}-generic`;
|
||||
fs.writeFileSync(packageFile, JSON.stringify(appConfig, null, 4), 'utf8');
|
||||
child_process.execSync(`npm run ${platform}`, {stdio: "inherit", cwd: "electron"});
|
||||
if (publish === true) {
|
||||
const publisher = createPublisher()
|
||||
if (publisher) {
|
||||
const outputDir = path.resolve(__dirname, appConfig.build.directories.output)
|
||||
if (fs.existsSync(outputDir)) {
|
||||
const apiPlatform = mapPlatform(platform)
|
||||
const files = fs.readdirSync(outputDir)
|
||||
for (const filename of files) {
|
||||
const localFile = path.join(outputDir, filename)
|
||||
const fileStat = fs.statSync(localFile)
|
||||
if (!fileStat.isFile()) continue
|
||||
|
||||
if (isInstaller(filename) && apiPlatform) {
|
||||
const arch = parseArchFromFilename(filename)
|
||||
await publisher.uploadPackage(localFile, { platform: apiPlatform, arch })
|
||||
} else {
|
||||
await publisher.uploadPackage(localFile)
|
||||
}
|
||||
}
|
||||
if (publish === true && r2.r2Configured()) {
|
||||
const client = r2.createR2Client()
|
||||
const outputDir = path.resolve(__dirname, appConfig.build.directories.output)
|
||||
if (fs.existsSync(outputDir)) {
|
||||
const files = fs.readdirSync(outputDir)
|
||||
for (const filename of files) {
|
||||
const localFile = path.join(outputDir, filename)
|
||||
if (!fs.statSync(localFile).isFile()) continue
|
||||
await uploadDraftFile(client, localFile, config.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -718,13 +553,13 @@ if (["dev"].includes(argv[2])) {
|
||||
}
|
||||
})
|
||||
} else if (["android-upload"].includes(argv[2])) {
|
||||
// 上传安卓文件(GitHub Actions)
|
||||
// 上传安卓文件到 R2 draft(GitHub Actions)
|
||||
(async () => {
|
||||
const publisher = createPublisher()
|
||||
if (!publisher) {
|
||||
console.error("缺少 UPLOAD_TOKEN 或 UPLOAD_URL 环境变量")
|
||||
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(__dirname, "../resources/mobile/platforms/android/eeuiApp/app/build/outputs/apk/release");
|
||||
if (!fs.existsSync(releaseDir)) {
|
||||
console.error("发布文件未找到")
|
||||
@ -734,7 +569,7 @@ if (["dev"].includes(argv[2])) {
|
||||
for (const filename of files) {
|
||||
const localFile = path.join(releaseDir, filename)
|
||||
if (/\.apk$/.test(filename) && fs.existsSync(localFile) && fs.statSync(localFile).isFile()) {
|
||||
await publisher.uploadPackage(localFile, { platform: 'android' })
|
||||
await uploadDraftFile(client, localFile, config.version)
|
||||
}
|
||||
}
|
||||
})().catch(err => {
|
||||
@ -742,24 +577,63 @@ if (["dev"].includes(argv[2])) {
|
||||
process.exit(1)
|
||||
})
|
||||
} else if (["release"].includes(argv[2])) {
|
||||
// 通知官网发布完成(GitHub Actions)
|
||||
// R2 内提升:draft/<version> → release/(当前版扁平,旧版归档 release/<prev>/)
|
||||
(async () => {
|
||||
const publisher = createPublisher()
|
||||
if (!publisher) {
|
||||
console.error("缺少 UPLOAD_TOKEN 或 UPLOAD_URL 环境变量")
|
||||
if (!r2.r2Configured()) {
|
||||
console.error("缺少 R2_* 环境变量")
|
||||
process.exit(1)
|
||||
}
|
||||
await publisher.release()
|
||||
const client = r2.createR2Client()
|
||||
const version = config.version
|
||||
const draftPrefix = `draft/${version}/`
|
||||
const draftKeys = await r2.listKeys(client, draftPrefix)
|
||||
if (!draftKeys.length) {
|
||||
console.error(`draft/${version}/ 为空,无法发布`)
|
||||
process.exit(1)
|
||||
}
|
||||
const names = draftKeys.map(k => k.slice(draftPrefix.length))
|
||||
|
||||
// 读 manifest 取上一发布版
|
||||
const manifest = JSON.parse(await r2.getText(client, 'manifest.json') || '{"draft":null,"release":null}')
|
||||
const prev = manifest.release
|
||||
|
||||
// 1. 归档上一版扁平文件 → release/<prev>/
|
||||
if (prev && prev !== version) {
|
||||
const prevRootKeys = await r2.listKeys(client, 'release/', '/')
|
||||
for (const key of prevRootKeys) {
|
||||
const name = key.slice('release/'.length)
|
||||
await r2.copyObject(client, key, `release/${prev}/${name}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 清空扁平根层(仅根层对象,版本归档子目录不动)
|
||||
const rootKeys = await r2.listKeys(client, 'release/', '/')
|
||||
await r2.deleteKeys(client, rootKeys)
|
||||
|
||||
// 3. 铺新扁平:安装包/blockmap/zip 先,latest*.yml 最后
|
||||
const ymls = names.filter(n => /\.ya?ml$/i.test(n))
|
||||
const others = names.filter(n => !/\.ya?ml$/i.test(n))
|
||||
for (const name of others) await r2.copyObject(client, `${draftPrefix}${name}`, `release/${name}`)
|
||||
for (const name of ymls) await r2.copyObject(client, `${draftPrefix}${name}`, `release/${name}`)
|
||||
|
||||
// 4. 下载索引
|
||||
const index = buildReleaseIndex(names)
|
||||
await r2.putText(client, 'release/index.json', JSON.stringify({ version, files: index }, null, 2))
|
||||
|
||||
// 5. 更新 manifest,清理 draft
|
||||
await r2.putText(client, 'manifest.json', JSON.stringify({ draft: null, release: version }, null, 2))
|
||||
await r2.deleteKeys(client, draftKeys)
|
||||
|
||||
console.log(`Release published: v${version}`)
|
||||
})().catch(err => {
|
||||
console.error(err.message || err)
|
||||
process.exit(1)
|
||||
})
|
||||
} else if (["upload-changelog"].includes(argv[2])) {
|
||||
// 上传 changelog(GitHub Actions)
|
||||
// 上传 changelog 到 R2(GitHub Actions)
|
||||
(async () => {
|
||||
const publisher = createPublisher()
|
||||
if (!publisher) {
|
||||
console.error("缺少 UPLOAD_TOKEN 或 UPLOAD_URL 环境变量")
|
||||
if (!r2.r2Configured()) {
|
||||
console.error("缺少 R2_* 环境变量")
|
||||
process.exit(1)
|
||||
}
|
||||
const changelogPath = path.resolve(__dirname, "../CHANGELOG.md")
|
||||
@ -767,8 +641,10 @@ if (["dev"].includes(argv[2])) {
|
||||
console.error("CHANGELOG.md 未找到")
|
||||
process.exit(1)
|
||||
}
|
||||
const client = r2.createR2Client()
|
||||
const content = fs.readFileSync(changelogPath, 'utf8')
|
||||
await publisher.uploadChangelog(content)
|
||||
await r2.putText(client, 'changelog.md', content)
|
||||
console.log('Changelog uploaded')
|
||||
})().catch(err => {
|
||||
console.error(err.message || err)
|
||||
process.exit(1)
|
||||
@ -922,8 +798,8 @@ if (["dev"].includes(argv[2])) {
|
||||
|
||||
// 发布判断环境变量
|
||||
if (answers.publish) {
|
||||
if (!(UPLOAD_TOKEN && UPLOAD_URL) && !(GITHUB_TOKEN && utils.strExists(GITHUB_REPOSITORY, "/"))) {
|
||||
console.error("发布需要 UPLOAD_TOKEN + UPLOAD_URL 或 GITHUB_TOKEN + GITHUB_REPOSITORY, 请检查环境变量!");
|
||||
if (!r2.r2Configured() && !(GITHUB_TOKEN && utils.strExists(GITHUB_REPOSITORY, "/"))) {
|
||||
console.error("发布需要 R2_* 或 GITHUB_TOKEN + GITHUB_REPOSITORY, 请检查环境变量!");
|
||||
process.exit()
|
||||
}
|
||||
}
|
||||
|
||||
133
electron/lib/r2.js
vendored
Normal file
133
electron/lib/r2.js
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
const fs = require('fs');
|
||||
const {
|
||||
S3Client,
|
||||
PutObjectCommand,
|
||||
GetObjectCommand,
|
||||
CopyObjectCommand,
|
||||
DeleteObjectsCommand,
|
||||
ListObjectsV2Command,
|
||||
} = require('@aws-sdk/client-s3');
|
||||
const { Upload } = require('@aws-sdk/lib-storage');
|
||||
|
||||
const {
|
||||
R2_ACCESS_KEY_ID,
|
||||
R2_SECRET_ACCESS_KEY,
|
||||
R2_ENDPOINT,
|
||||
R2_BUCKET,
|
||||
R2_PUBLIC_URL,
|
||||
} = process.env;
|
||||
|
||||
function r2Configured() {
|
||||
return !!(R2_ACCESS_KEY_ID && R2_SECRET_ACCESS_KEY && R2_ENDPOINT && R2_BUCKET);
|
||||
}
|
||||
|
||||
function createR2Client() {
|
||||
return new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: R2_ENDPOINT,
|
||||
credentials: {
|
||||
accessKeyId: R2_ACCESS_KEY_ID,
|
||||
secretAccessKey: R2_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function contentTypeFor(name) {
|
||||
if (/\.ya?ml$/i.test(name)) return 'text/yaml';
|
||||
if (/\.json$/i.test(name)) return 'application/json';
|
||||
if (/\.md$/i.test(name)) return 'text/markdown; charset=utf-8';
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
/** 流式上传本地文件,onProgress(loaded, total) */
|
||||
async function uploadFile(client, localFile, key, onProgress) {
|
||||
const total = fs.statSync(localFile).size;
|
||||
const upload = new Upload({
|
||||
client,
|
||||
params: {
|
||||
Bucket: R2_BUCKET,
|
||||
Key: key,
|
||||
Body: fs.createReadStream(localFile),
|
||||
ContentType: contentTypeFor(key),
|
||||
},
|
||||
});
|
||||
if (onProgress) {
|
||||
upload.on('httpUploadProgress', (p) => onProgress(p.loaded || 0, total));
|
||||
}
|
||||
await upload.done();
|
||||
}
|
||||
|
||||
/** 写入文本对象 */
|
||||
async function putText(client, key, text) {
|
||||
await client.send(new PutObjectCommand({
|
||||
Bucket: R2_BUCKET,
|
||||
Key: key,
|
||||
Body: text,
|
||||
ContentType: contentTypeFor(key),
|
||||
}));
|
||||
}
|
||||
|
||||
/** 读取文本对象,不存在返回 null */
|
||||
async function getText(client, key) {
|
||||
try {
|
||||
const res = await client.send(new GetObjectCommand({ Bucket: R2_BUCKET, Key: key }));
|
||||
return await res.Body.transformToString();
|
||||
} catch (err) {
|
||||
if (err.name === 'NoSuchKey' || err.$metadata?.httpStatusCode === 404) return null;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/** 桶内服务端复制(文件名为安全 ASCII,无需额外编码) */
|
||||
async function copyObject(client, srcKey, destKey) {
|
||||
await client.send(new CopyObjectCommand({
|
||||
Bucket: R2_BUCKET,
|
||||
CopySource: `${R2_BUCKET}/${srcKey}`,
|
||||
Key: destKey,
|
||||
ContentType: contentTypeFor(destKey),
|
||||
MetadataDirective: 'REPLACE',
|
||||
}));
|
||||
}
|
||||
|
||||
/** 列举 key;delimiter='/' 时仅返回该前缀下的根层对象(子目录归 CommonPrefixes,不返回) */
|
||||
async function listKeys(client, prefix, delimiter) {
|
||||
const keys = [];
|
||||
let token;
|
||||
do {
|
||||
const res = await client.send(new ListObjectsV2Command({
|
||||
Bucket: R2_BUCKET,
|
||||
Prefix: prefix,
|
||||
Delimiter: delimiter,
|
||||
ContinuationToken: token,
|
||||
}));
|
||||
for (const o of res.Contents || []) keys.push(o.Key);
|
||||
token = res.IsTruncated ? res.NextContinuationToken : undefined;
|
||||
} while (token);
|
||||
return keys;
|
||||
}
|
||||
|
||||
/** 批量删除(每批 1000) */
|
||||
async function deleteKeys(client, keys) {
|
||||
for (let i = 0; i < keys.length; i += 1000) {
|
||||
const batch = keys.slice(i, i + 1000);
|
||||
if (!batch.length) continue;
|
||||
await client.send(new DeleteObjectsCommand({
|
||||
Bucket: R2_BUCKET,
|
||||
Delete: { Objects: batch.map((Key) => ({ Key })) },
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
r2Configured,
|
||||
createR2Client,
|
||||
contentTypeFor,
|
||||
uploadFile,
|
||||
putText,
|
||||
getText,
|
||||
copyObject,
|
||||
listKeys,
|
||||
deleteKeys,
|
||||
R2_BUCKET,
|
||||
R2_PUBLIC_URL,
|
||||
};
|
||||
46
electron/lib/release-index.js
vendored
Normal file
46
electron/lib/release-index.js
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
// 仅这些扩展名进入下载索引(排除 .zip:mac 自动更新增量包,非下载按钮目标)
|
||||
const DOWNLOAD_EXTS = ['.dmg', '.exe', '.msi', '.appimage', '.deb', '.rpm', '.apk', '.pkg'];
|
||||
|
||||
/**
|
||||
* 从文件名解析 platform/arch(与官网 storage.ts 规则保持一致)
|
||||
* @returns {{platform: string, arch: string|null}|null}
|
||||
*/
|
||||
function parseFilename(filename) {
|
||||
const lower = filename.toLowerCase();
|
||||
if (lower.endsWith('.apk')) {
|
||||
return { platform: 'android', arch: null };
|
||||
}
|
||||
if (!DOWNLOAD_EXTS.some((ext) => lower.endsWith(ext))) {
|
||||
return null;
|
||||
}
|
||||
let platform = null;
|
||||
if (/-mac-/i.test(filename) || lower.endsWith('.dmg') || lower.endsWith('.pkg')) {
|
||||
platform = 'mac';
|
||||
} else if (/-win-/i.test(filename) || /-win\./i.test(filename) || lower.endsWith('.msi')) {
|
||||
platform = 'win';
|
||||
} else if (/-linux-/i.test(filename) || lower.endsWith('.appimage') || lower.endsWith('.deb') || lower.endsWith('.rpm')) {
|
||||
platform = 'linux';
|
||||
}
|
||||
if (!platform) return null;
|
||||
let arch = null;
|
||||
if (/-arm64[.-]/i.test(filename)) arch = 'arm64';
|
||||
else if (/-x64[.-]/i.test(filename)) arch = 'x64';
|
||||
return { platform, arch };
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成下载索引:{ "<platform>": { "<arch|default>": filename } }
|
||||
*/
|
||||
function buildReleaseIndex(filenames) {
|
||||
const index = {};
|
||||
for (const filename of filenames) {
|
||||
const parsed = parseFilename(filename);
|
||||
if (!parsed) continue;
|
||||
const archKey = parsed.arch || 'default';
|
||||
index[parsed.platform] = index[parsed.platform] || {};
|
||||
index[parsed.platform][archKey] = filename;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
module.exports = { parseFilename, buildReleaseIndex, DOWNLOAD_EXTS };
|
||||
36
electron/lib/release-index.test.js
vendored
Normal file
36
electron/lib/release-index.test.js
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { parseFilename, buildReleaseIndex } = require('./release-index');
|
||||
|
||||
test('parseFilename: win exe x64', () => {
|
||||
assert.deepStrictEqual(parseFilename('DooTask-v1.7.56-win-x64.exe'), { platform: 'win', arch: 'x64' });
|
||||
});
|
||||
|
||||
test('parseFilename: mac dmg arm64', () => {
|
||||
assert.deepStrictEqual(parseFilename('DooTask-v1.7.56-mac-arm64.dmg'), { platform: 'mac', arch: 'arm64' });
|
||||
});
|
||||
|
||||
test('parseFilename: android apk has null arch', () => {
|
||||
assert.deepStrictEqual(parseFilename('app-release.apk'), { platform: 'android', arch: null });
|
||||
});
|
||||
|
||||
test('parseFilename: ignores yml/blockmap/zip', () => {
|
||||
assert.strictEqual(parseFilename('latest.yml'), null);
|
||||
assert.strictEqual(parseFilename('DooTask-v1.7.56-win-x64.exe.blockmap'), null);
|
||||
assert.strictEqual(parseFilename('DooTask-v1.7.56-mac-arm64.zip'), null);
|
||||
});
|
||||
|
||||
test('buildReleaseIndex: groups by platform/arch, .zip never overwrites .dmg', () => {
|
||||
const index = buildReleaseIndex([
|
||||
'DooTask-v1.7.56-mac-arm64.dmg',
|
||||
'DooTask-v1.7.56-mac-arm64.zip',
|
||||
'DooTask-v1.7.56-win-x64.exe',
|
||||
'latest.yml',
|
||||
'app-release.apk',
|
||||
]);
|
||||
assert.deepStrictEqual(index, {
|
||||
mac: { arm64: 'DooTask-v1.7.56-mac-arm64.dmg' },
|
||||
win: { x64: 'DooTask-v1.7.56-win-x64.exe' },
|
||||
android: { default: 'app-release.apk' },
|
||||
});
|
||||
});
|
||||
@ -42,6 +42,8 @@
|
||||
"ora": "^4.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1052.0",
|
||||
"@aws-sdk/lib-storage": "^3.1052.0",
|
||||
"@dootask/electron-dl": "^4.0.0-rc.2",
|
||||
"axios": "^1.11.0",
|
||||
"crc": "^3.8.0",
|
||||
@ -60,8 +62,8 @@
|
||||
"request": "^2.88.2",
|
||||
"tar": "^7.4.3",
|
||||
"turndown": "^7.2.2",
|
||||
"zod": "^3.23.8",
|
||||
"yauzl": "^3.2.0"
|
||||
"yauzl": "^3.2.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"trayIcon": {
|
||||
"dev": {
|
||||
|
||||
@ -560,8 +560,6 @@ webhook地址最长仅支持255个字符。
|
||||
(*)将(*)移出群组
|
||||
(*)退出群组
|
||||
(*)已加入群组
|
||||
(*)当前正在共享,无法移动到另一个共享文件夹内
|
||||
(*)内含有共享文件,无法移动到另一个共享文件夹内
|
||||
处理错误
|
||||
仅限所有者或创建者操作
|
||||
没有修改写入权限
|
||||
@ -575,13 +573,11 @@ webhook地址最长仅支持255个字符。
|
||||
子任务负责人填写错误
|
||||
任务负责人填写错误
|
||||
(*)负责或参与的未完成任务最多不能超过(*)个
|
||||
(*)已被其他成员设置
|
||||
邮件发送超时,请检查邮箱配置是否正确
|
||||
群主不可移出
|
||||
部门成员、项目人员或任务人员不可移出
|
||||
群主不可退出
|
||||
部门成员、项目人员或任务人员不可退出
|
||||
当前客户端版本(*)过低,最低版本要求(*)。
|
||||
验证码不能为空
|
||||
别名不能为空
|
||||
别名的长度在(*)个字符
|
||||
@ -607,7 +603,6 @@ webhook地址最长仅支持255个字符。
|
||||
(*)的周报[(*)][(*)月第(*)周]
|
||||
(*)的日报[(*)]
|
||||
考勤机
|
||||
手动签到
|
||||
|
||||
(*)评论了(*)的「(**)」审批
|
||||
抄送(*)提交的「(**)」记录
|
||||
@ -615,7 +610,6 @@ webhook地址最长仅支持255个字符。
|
||||
您发起的「(**)」已通过
|
||||
您发起的「(**)」被(*)拒绝
|
||||
|
||||
消息不存在或已被删除
|
||||
此消息不支持翻译
|
||||
消息内容为空
|
||||
翻译失败
|
||||
@ -629,26 +623,15 @@ webhook地址最长仅支持255个字符。
|
||||
任务结束时间
|
||||
任务计划用时
|
||||
超时时间
|
||||
负责人
|
||||
创建人
|
||||
|
||||
(*)等(*)位成员的任务统计
|
||||
(*)的任务统计
|
||||
任务ID
|
||||
父级任务ID
|
||||
所属项目
|
||||
任务标题
|
||||
任务开始时间
|
||||
任务结束时间
|
||||
完成时间
|
||||
归档时间
|
||||
任务计划用时
|
||||
实际完成用时
|
||||
超时时间
|
||||
开发用时
|
||||
验收/测试用时
|
||||
负责人
|
||||
创建人
|
||||
状态
|
||||
|
||||
审批记录
|
||||
@ -656,7 +639,6 @@ webhook地址最长仅支持255个字符。
|
||||
标题
|
||||
申请状态
|
||||
发起时间
|
||||
完成时间
|
||||
发起人工号
|
||||
发起人User ID
|
||||
发起人姓名
|
||||
@ -665,7 +647,6 @@ webhook地址最长仅支持255个字符。
|
||||
部门负责人
|
||||
历史审批人
|
||||
历史办理人
|
||||
审批记录
|
||||
当前处理人
|
||||
审批节点
|
||||
审批人数
|
||||
@ -819,7 +800,6 @@ AI机器人不存在
|
||||
|
||||
选择模型
|
||||
当前对话不支持
|
||||
会话不存在或已被删除
|
||||
开启新会话
|
||||
历史会话
|
||||
|
||||
@ -839,13 +819,11 @@ AI机器人不存在
|
||||
(*)天(*)小时(*)分钟
|
||||
(*)天(*)小时
|
||||
(*)天(*)分钟
|
||||
(*)天
|
||||
(*)小时(*)分钟
|
||||
(*)小时
|
||||
(*)分钟
|
||||
|
||||
任务不存在或已被删除
|
||||
文件不存在或已被删除
|
||||
报告不存在或已被删除
|
||||
文件读取失败:(*)
|
||||
|
||||
@ -919,7 +897,6 @@ URL格式不正确
|
||||
报告内容为空,无法进行分析
|
||||
工作汇报分析失败
|
||||
工作汇报分析结果为空
|
||||
缺少ID参数
|
||||
无权访问该工作汇报
|
||||
生成AI分析失败
|
||||
工作汇报内容不能为空
|
||||
@ -937,14 +914,11 @@ URL格式不正确
|
||||
会员不存在
|
||||
请输入个性标签
|
||||
标签名称最多只能设置(*)个字
|
||||
标签已存在
|
||||
每位会员最多添加(*)个标签
|
||||
参数错误
|
||||
标签不存在
|
||||
无权操作该标签
|
||||
已取消认可
|
||||
认可成功
|
||||
选择模型
|
||||
请先配置 AI 助手
|
||||
请先在「AI 助手」设置中配置 (*)
|
||||
今日未完成的工作
|
||||
@ -974,3 +948,31 @@ AI 返回内容为空
|
||||
没有权限操作此任务
|
||||
请选择要转发的消息
|
||||
LDAP 用户缺少邮箱属性,请联系管理员配置
|
||||
群管理员
|
||||
任命群管理员
|
||||
罢免群管理员
|
||||
该用户不是群成员
|
||||
不能将群主任命为群管理员
|
||||
仅群主或群管理员可操作
|
||||
仅限群主或群管理员操作
|
||||
群管理员不能移出群主或其他群管理员
|
||||
请选择有效的成员
|
||||
任命成功
|
||||
罢免成功
|
||||
项目管理员
|
||||
任命项目管理员
|
||||
罢免项目管理员
|
||||
该用户不是项目成员
|
||||
不能将负责人任命为项目管理员
|
||||
不能将部门负责人任命为部门管理员
|
||||
该用户不存在
|
||||
无权操作此模板
|
||||
修改共享模板
|
||||
修改负责人视角可见
|
||||
项目负责人数据异常,请先修复项目负责人
|
||||
项目成员列表必须包含项目负责人
|
||||
项目管理员不能移除项目负责人或项目管理员
|
||||
项目管理员必须是项目成员
|
||||
负责人不能任命为项目管理员
|
||||
普通成员不能移出群主或群管理员
|
||||
只有群主、群管理员或邀请人可以移出成员
|
||||
|
||||
@ -1689,7 +1689,6 @@ WiFi签到延迟时长为±1分钟。
|
||||
|
||||
该任务尚未被领取,点击这里
|
||||
考勤机
|
||||
手动签到
|
||||
签到备注
|
||||
重复打卡提醒
|
||||
|
||||
@ -1728,7 +1727,6 @@ WiFi签到延迟时长为±1分钟。
|
||||
插入链接
|
||||
请输入完整的链接地址
|
||||
|
||||
自动通过,审批人与发起人为同一人
|
||||
自动通过,审批人已审核
|
||||
|
||||
你确定要删除项目吗?
|
||||
@ -1757,9 +1755,6 @@ WiFi签到延迟时长为±1分钟。
|
||||
定位失败
|
||||
位置
|
||||
你选择的位置「(*)」不在签到范围内
|
||||
定位签到
|
||||
通过在签到打卡机器人发送位置签到
|
||||
签到备注
|
||||
百度地图AK
|
||||
腾讯地图Key
|
||||
高德地图Key
|
||||
@ -1813,7 +1808,6 @@ WiFi签到延迟时长为±1分钟。
|
||||
系统别名
|
||||
用于网页默认标题、邮件发送等
|
||||
|
||||
权限设置
|
||||
打包权限
|
||||
允许所有人
|
||||
仅限管理员
|
||||
@ -1882,7 +1876,6 @@ WiFi签到延迟时长为±1分钟。
|
||||
只有在项目中才能创建任务
|
||||
项目不存在
|
||||
只有在任务中才能创建子任务
|
||||
任务不存在
|
||||
未知类型
|
||||
未找到内容
|
||||
再见
|
||||
@ -1901,7 +1894,6 @@ WiFi签到延迟时长为±1分钟。
|
||||
请输入标签描述
|
||||
标签颜色
|
||||
使用示例标签
|
||||
取消默认
|
||||
编辑标签
|
||||
确定要删除该标签吗?
|
||||
标签
|
||||
@ -1909,7 +1901,6 @@ WiFi签到延迟时长为±1分钟。
|
||||
暂无标签
|
||||
添加标签
|
||||
请选择示例标签
|
||||
全部保存成功
|
||||
|
||||
消息详情
|
||||
长文本
|
||||
@ -2022,7 +2013,6 @@ API请求的URL路径
|
||||
附言
|
||||
|
||||
任务不存在或已被删除
|
||||
文件不存在或已被删除
|
||||
报告不存在或已被删除
|
||||
文件读取失败:(*)
|
||||
独立窗口显示
|
||||
@ -2037,7 +2027,6 @@ API请求的URL路径
|
||||
删除机器人:(*)
|
||||
|
||||
默认:90天
|
||||
机器人名称
|
||||
|
||||
后退
|
||||
前进
|
||||
@ -2089,12 +2078,8 @@ OKR群组
|
||||
会话名称
|
||||
值
|
||||
结果
|
||||
名称
|
||||
命令
|
||||
必填
|
||||
接口地址
|
||||
清理时间
|
||||
类型
|
||||
该机器人不支持
|
||||
说明
|
||||
属性
|
||||
@ -2214,7 +2199,6 @@ Webhook事件
|
||||
打开会话
|
||||
成员加入
|
||||
成员退出
|
||||
是否拨打电话给(*)?
|
||||
是否发送邮件给(*)?
|
||||
|
||||
个人信息
|
||||
@ -2283,7 +2267,6 @@ URL不能为空
|
||||
AI 搜索
|
||||
AI 项目助手
|
||||
AI 汇报分析
|
||||
AI 整理汇报
|
||||
AI 任务助手
|
||||
AI 消息助手
|
||||
欢迎使用 AI 助手
|
||||
@ -2344,7 +2327,6 @@ AI任务分析
|
||||
关闭后所有项目将不再自动分析任务。
|
||||
关闭后本项目将不再自动分析任务。
|
||||
新建任务后AI自动分析并给出建议。
|
||||
关闭后本项目将不再自动分析任务。
|
||||
(最多(*)条)
|
||||
最多选择(*)条消息
|
||||
系统已关闭AI任务分析功能。
|
||||
@ -2361,3 +2343,53 @@ AI任务分析
|
||||
登录属性
|
||||
用于匹配登录用户名的 LDAP 属性,Active Directory 请选择 sAMAccountName
|
||||
请输入帐号
|
||||
群管理员
|
||||
任命群管理员
|
||||
罢免群管理员
|
||||
确定要罢免该群管理员吗?
|
||||
还没有群管理员
|
||||
添加群管理员
|
||||
确定将 (*) 任命为群管理员吗?
|
||||
项目管理员
|
||||
任命项目管理员
|
||||
罢免项目管理员
|
||||
确定要罢免该项目管理员吗?
|
||||
还没有项目管理员
|
||||
添加项目管理员
|
||||
确定将 (*) 任命为项目管理员吗?
|
||||
部门管理员
|
||||
任命部门管理员
|
||||
罢免部门管理员
|
||||
请选择部门管理员
|
||||
确定将 (*) 任命为部门管理员吗?
|
||||
部门管理员享有部门群的群管理员权限
|
||||
即将罢免项目管理员
|
||||
请确认以下操作,注意此操作不可逆!
|
||||
移除成员负责的任务将变成无负责人。
|
||||
搜索模板
|
||||
来自(*)
|
||||
暂无可用模板
|
||||
加载中
|
||||
共享模板
|
||||
开启后,添加任务时可使用其他项目共享的任务模板。
|
||||
关闭后,添加任务时仅加载本项目模板,不显示其他项目共享模板。
|
||||
根据系统设置的自动归档规则执行
|
||||
负责人视角
|
||||
开启后,部门负责人可只读查看本项目及其全员可见任务。
|
||||
关闭后,本项目及其群聊对部门负责人视角隐藏。
|
||||
个人项目,只读查看
|
||||
负责人视角,只读查看
|
||||
我的项目
|
||||
没有任何与"(*)"相关的结果
|
||||
标记未选
|
||||
标记已选
|
||||
可查看所选部门及所有下级部门成员参与的项目和任务,仅支持只读查看。
|
||||
反选
|
||||
切换失败
|
||||
当前为负责人视角:你可查看项目和任务,并参与讨论,但不能编辑项目或任务。
|
||||
选择项目管理员
|
||||
即将移除
|
||||
当前为负责人,并参与讨论,但不能编辑任务。
|
||||
部门负责人视角
|
||||
开启后,部门负责人/部门管理员可只读查看本部门及下级部门成员参与的项目和项目内全部任务。
|
||||
部门管理员同步失败
|
||||
|
||||
@ -33694,5 +33694,869 @@
|
||||
"fr": "Il manque l’attribut e-mail pour l’utilisateur LDAP. Veuillez contacter l’administrateur pour le configurer.",
|
||||
"id": "Pengguna LDAP tidak memiliki atribut email. Silakan hubungi administrator untuk mengonfigurasinya.",
|
||||
"ru": "У пользователя LDAP отсутствует атрибут электронной почты. Пожалуйста, свяжитесь с администратором для настройки."
|
||||
},
|
||||
{
|
||||
"key": "群管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "群組管理員",
|
||||
"en": "Group Administrator",
|
||||
"ko": "그룹 관리자",
|
||||
"ja": "グループ管理者",
|
||||
"de": "Gruppenadministrator",
|
||||
"fr": "Administrateur du groupe",
|
||||
"id": "Administrator Grup",
|
||||
"ru": "Администратор группы"
|
||||
},
|
||||
{
|
||||
"key": "任命群管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "任命群組管理員",
|
||||
"en": "Appoint Group Administrator",
|
||||
"ko": "그룹 관리자 임명",
|
||||
"ja": "グループ管理者を任命",
|
||||
"de": "Gruppenadministrator ernennen",
|
||||
"fr": "Nommer un administrateur du groupe",
|
||||
"id": "Tunjuk Administrator Grup",
|
||||
"ru": "Назначить администратора группы"
|
||||
},
|
||||
{
|
||||
"key": "罢免群管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "罷免群組管理員",
|
||||
"en": "Remove Group Administrator",
|
||||
"ko": "그룹 관리자 해임",
|
||||
"ja": "グループ管理者を解任",
|
||||
"de": "Gruppenadministrator abberufen",
|
||||
"fr": "Révoquer l’administrateur du groupe",
|
||||
"id": "Cabut Administrator Grup",
|
||||
"ru": "Снять администратора группы"
|
||||
},
|
||||
{
|
||||
"key": "确定要罢免该群管理员吗?",
|
||||
"zh": "",
|
||||
"zh-CHT": "確定要罷免該群組管理員嗎?",
|
||||
"en": "Are you sure you want to remove this group administrator?",
|
||||
"ko": "이 그룹 관리자를 해임하시겠습니까?",
|
||||
"ja": "このグループ管理者を解任してもよろしいですか?",
|
||||
"de": "Möchten Sie diesen Gruppenadministrator wirklich abberufen?",
|
||||
"fr": "Voulez-vous vraiment révoquer cet administrateur du groupe ?",
|
||||
"id": "Apakah Anda yakin ingin mencabut administrator grup ini?",
|
||||
"ru": "Вы уверены, что хотите снять этого администратора группы?"
|
||||
},
|
||||
{
|
||||
"key": "还没有群管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "還沒有群組管理員",
|
||||
"en": "No group administrators yet",
|
||||
"ko": "아직 그룹 관리자가 없습니다",
|
||||
"ja": "グループ管理者はまだいません",
|
||||
"de": "Noch keine Gruppenadministratoren",
|
||||
"fr": "Aucun administrateur du groupe pour le moment",
|
||||
"id": "Belum ada administrator grup",
|
||||
"ru": "Администраторы группы пока отсутствуют"
|
||||
},
|
||||
{
|
||||
"key": "添加群管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "新增群組管理員",
|
||||
"en": "Add Group Administrator",
|
||||
"ko": "그룹 관리자 추가",
|
||||
"ja": "グループ管理者を追加",
|
||||
"de": "Gruppenadministrator hinzufügen",
|
||||
"fr": "Ajouter un administrateur du groupe",
|
||||
"id": "Tambahkan Administrator Grup",
|
||||
"ru": "Добавить администратора группы"
|
||||
},
|
||||
{
|
||||
"key": "确定将 (%T1) 任命为群管理员吗?",
|
||||
"zh": "",
|
||||
"zh-CHT": "確定將 (%T1) 任命為群組管理員嗎?",
|
||||
"en": "Are you sure you want to appoint (%T1) as a group administrator?",
|
||||
"ko": "(%T1)을(를) 그룹 관리자로 임명하시겠습니까?",
|
||||
"ja": "(%T1) をグループ管理者に任命してもよろしいですか?",
|
||||
"de": "Möchten Sie (%T1) wirklich zum Gruppenadministrator ernennen?",
|
||||
"fr": "Voulez-vous vraiment nommer (%T1) comme administrateur du groupe ?",
|
||||
"id": "Apakah Anda yakin ingin menunjuk (%T1) sebagai administrator grup?",
|
||||
"ru": "Вы уверены, что хотите назначить (%T1) администратором группы?"
|
||||
},
|
||||
{
|
||||
"key": "项目管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "專案管理員",
|
||||
"en": "Project Administrator",
|
||||
"ko": "프로젝트 관리자",
|
||||
"ja": "プロジェクト管理者",
|
||||
"de": "Projektadministrator",
|
||||
"fr": "Administrateur du projet",
|
||||
"id": "Administrator Proyek",
|
||||
"ru": "Администратор проекта"
|
||||
},
|
||||
{
|
||||
"key": "任命项目管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "任命專案管理員",
|
||||
"en": "Appoint Project Administrator",
|
||||
"ko": "프로젝트 관리자 임명",
|
||||
"ja": "プロジェクト管理者を任命",
|
||||
"de": "Projektadministrator ernennen",
|
||||
"fr": "Nommer un administrateur du projet",
|
||||
"id": "Tunjuk Administrator Proyek",
|
||||
"ru": "Назначить администратора проекта"
|
||||
},
|
||||
{
|
||||
"key": "罢免项目管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "罷免專案管理員",
|
||||
"en": "Remove Project Administrator",
|
||||
"ko": "프로젝트 관리자 해임",
|
||||
"ja": "プロジェクト管理者を解任",
|
||||
"de": "Projektadministrator abberufen",
|
||||
"fr": "Révoquer l’administrateur du projet",
|
||||
"id": "Cabut Administrator Proyek",
|
||||
"ru": "Снять администратора проекта"
|
||||
},
|
||||
{
|
||||
"key": "确定要罢免该项目管理员吗?",
|
||||
"zh": "",
|
||||
"zh-CHT": "確定要罷免該專案管理員嗎?",
|
||||
"en": "Are you sure you want to remove this project administrator?",
|
||||
"ko": "이 프로젝트 관리자를 해임하시겠습니까?",
|
||||
"ja": "このプロジェクト管理者を解任してもよろしいですか?",
|
||||
"de": "Möchten Sie diesen Projektadministrator wirklich abberufen?",
|
||||
"fr": "Voulez-vous vraiment révoquer cet administrateur de projet ?",
|
||||
"id": "Yakin ingin mencabut jabatan administrator proyek ini?",
|
||||
"ru": "Вы уверены, что хотите снять этого администратора проекта?"
|
||||
},
|
||||
{
|
||||
"key": "还没有项目管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "還沒有專案管理員",
|
||||
"en": "No project administrator yet",
|
||||
"ko": "아직 프로젝트 관리자가 없습니다",
|
||||
"ja": "プロジェクト管理者はまだいません",
|
||||
"de": "Noch kein Projektadministrator vorhanden",
|
||||
"fr": "Aucun administrateur de projet pour le moment",
|
||||
"id": "Belum ada administrator proyek",
|
||||
"ru": "Администратор проекта еще не назначен"
|
||||
},
|
||||
{
|
||||
"key": "添加项目管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "新增專案管理員",
|
||||
"en": "Add project administrator",
|
||||
"ko": "프로젝트 관리자 추가",
|
||||
"ja": "プロジェクト管理者を追加",
|
||||
"de": "Projektadministrator hinzufügen",
|
||||
"fr": "Ajouter un administrateur de projet",
|
||||
"id": "Tambahkan administrator proyek",
|
||||
"ru": "Добавить администратора проекта"
|
||||
},
|
||||
{
|
||||
"key": "确定将 (%T1) 任命为项目管理员吗?",
|
||||
"zh": "",
|
||||
"zh-CHT": "確定將 (%T1) 任命為專案管理員嗎?",
|
||||
"en": "Are you sure you want to appoint (%T1) as a project administrator?",
|
||||
"ko": "(%T1)을(를) 프로젝트 관리자로 임명하시겠습니까?",
|
||||
"ja": "(%T1) をプロジェクト管理者に任命してもよろしいですか?",
|
||||
"de": "Möchten Sie (%T1) wirklich zum Projektadministrator ernennen?",
|
||||
"fr": "Voulez-vous vraiment nommer (%T1) administrateur de projet ?",
|
||||
"id": "Yakin ingin menunjuk (%T1) sebagai administrator proyek?",
|
||||
"ru": "Вы уверены, что хотите назначить (%T1) администратором проекта?"
|
||||
},
|
||||
{
|
||||
"key": "部门管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "部門管理員",
|
||||
"en": "Department administrator",
|
||||
"ko": "부서 관리자",
|
||||
"ja": "部門管理者",
|
||||
"de": "Abteilungsadministrator",
|
||||
"fr": "Administrateur de département",
|
||||
"id": "Administrator departemen",
|
||||
"ru": "Администратор отдела"
|
||||
},
|
||||
{
|
||||
"key": "任命部门管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "任命部門管理員",
|
||||
"en": "Appoint department administrator",
|
||||
"ko": "부서 관리자 임명",
|
||||
"ja": "部門管理者を任命",
|
||||
"de": "Abteilungsadministrator ernennen",
|
||||
"fr": "Nommer un administrateur de département",
|
||||
"id": "Tunjuk administrator departemen",
|
||||
"ru": "Назначить администратора отдела"
|
||||
},
|
||||
{
|
||||
"key": "罢免部门管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "罷免部門管理員",
|
||||
"en": "Remove department administrator",
|
||||
"ko": "부서 관리자 해임",
|
||||
"ja": "部門管理者を解任",
|
||||
"de": "Abteilungsadministrator abberufen",
|
||||
"fr": "Révoquer l’administrateur de département",
|
||||
"id": "Cabut jabatan administrator departemen",
|
||||
"ru": "Снять администратора отдела"
|
||||
},
|
||||
{
|
||||
"key": "请选择部门管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "請選擇部門管理員",
|
||||
"en": "Please select a department administrator",
|
||||
"ko": "부서 관리자를 선택하세요",
|
||||
"ja": "部門管理者を選択してください",
|
||||
"de": "Bitte wählen Sie einen Abteilungsadministrator aus",
|
||||
"fr": "Veuillez sélectionner un administrateur de département",
|
||||
"id": "Silakan pilih administrator departemen",
|
||||
"ru": "Пожалуйста, выберите администратора отдела"
|
||||
},
|
||||
{
|
||||
"key": "确定将 (%T1) 任命为部门管理员吗?",
|
||||
"zh": "",
|
||||
"zh-CHT": "確定將 (%T1) 任命為部門管理員嗎?",
|
||||
"en": "Are you sure you want to appoint (%T1) as a department administrator?",
|
||||
"ko": "(%T1)을(를) 부서 관리자로 임명하시겠습니까?",
|
||||
"ja": "(%T1) を部門管理者に任命してもよろしいですか?",
|
||||
"de": "Möchten Sie (%T1) wirklich zum Abteilungsadministrator ernennen?",
|
||||
"fr": "Voulez-vous vraiment nommer (%T1) administrateur de département ?",
|
||||
"id": "Yakin ingin menunjuk (%T1) sebagai administrator departemen?",
|
||||
"ru": "Вы уверены, что хотите назначить (%T1) администратором отдела?"
|
||||
},
|
||||
{
|
||||
"key": "部门管理员享有部门群的群管理员权限",
|
||||
"zh": "",
|
||||
"zh-CHT": "部門管理員享有部門群的群管理員權限",
|
||||
"en": "Department administrators have group administrator permissions for the department group",
|
||||
"ko": "부서 관리자는 부서 그룹의 그룹 관리자 권한을 가집니다",
|
||||
"ja": "部門管理者は部門グループのグループ管理者権限を持ちます",
|
||||
"de": "Abteilungsadministratoren verfügen über Gruppenadministratorberechtigungen für die Abteilungsgruppe",
|
||||
"fr": "Les administrateurs de département disposent des droits d’administrateur du groupe de département",
|
||||
"id": "Administrator departemen memiliki izin administrator grup untuk grup departemen",
|
||||
"ru": "Администраторы отдела имеют права администратора группы отдела"
|
||||
},
|
||||
{
|
||||
"key": "即将罢免项目管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "即將罷免專案管理員",
|
||||
"en": "About to remove the project administrator",
|
||||
"ko": "프로젝트 관리자를 해임하려고 합니다",
|
||||
"ja": "プロジェクト管理者を解任しようとしています",
|
||||
"de": "Projektadministrator wird entfernt",
|
||||
"fr": "Vous êtes sur le point de révoquer l’administrateur du projet",
|
||||
"id": "Akan mencabut administrator proyek",
|
||||
"ru": "Сейчас будет отстранен администратор проекта"
|
||||
},
|
||||
{
|
||||
"key": "请确认以下操作,注意此操作不可逆!",
|
||||
"zh": "",
|
||||
"zh-CHT": "請確認以下操作,注意此操作不可逆!",
|
||||
"en": "Please confirm the following action. Note that this action cannot be undone!",
|
||||
"ko": "다음 작업을 확인해 주세요. 이 작업은 되돌릴 수 없습니다!",
|
||||
"ja": "以下の操作を確認してください。この操作は元に戻せません!",
|
||||
"de": "Bitte bestätigen Sie die folgende Aktion. Beachten Sie, dass diese Aktion nicht rückgängig gemacht werden kann!",
|
||||
"fr": "Veuillez confirmer l’opération suivante. Notez que cette opération est irréversible !",
|
||||
"id": "Harap konfirmasi tindakan berikut. Perlu diperhatikan bahwa tindakan ini tidak dapat dibatalkan!",
|
||||
"ru": "Подтвердите следующее действие. Обратите внимание: это действие необратимо!"
|
||||
},
|
||||
{
|
||||
"key": "移除成员负责的任务将变成无负责人。",
|
||||
"zh": "",
|
||||
"zh-CHT": "移除成員負責的任務將變成無負責人。",
|
||||
"en": "Tasks assigned to the removed member will become unassigned.",
|
||||
"ko": "제거된 멤버가 담당하던 작업은 담당자 없음 상태가 됩니다.",
|
||||
"ja": "削除されたメンバーが担当していたタスクは担当者なしになります。",
|
||||
"de": "Aufgaben, für die das entfernte Mitglied verantwortlich war, werden nicht mehr zugewiesen sein.",
|
||||
"fr": "Les tâches dont le membre supprimé était responsable deviendront non assignées.",
|
||||
"id": "Tugas yang menjadi tanggung jawab anggota yang dihapus akan menjadi tanpa penanggung jawab.",
|
||||
"ru": "Задачи, за которые отвечал удаленный участник, станут неназначенными."
|
||||
},
|
||||
{
|
||||
"key": "搜索模板",
|
||||
"zh": "",
|
||||
"zh-CHT": "搜尋範本",
|
||||
"en": "Search templates",
|
||||
"ko": "템플릿 검색",
|
||||
"ja": "テンプレートを検索",
|
||||
"de": "Vorlagen suchen",
|
||||
"fr": "Rechercher des modèles",
|
||||
"id": "Cari templat",
|
||||
"ru": "Поиск шаблонов"
|
||||
},
|
||||
{
|
||||
"key": "来自(%T1)",
|
||||
"zh": "",
|
||||
"zh-CHT": "來自(%T1)",
|
||||
"en": "From (%T1)",
|
||||
"ko": "(%T1)에서 제공",
|
||||
"ja": "(%T1)から",
|
||||
"de": "Von (%T1)",
|
||||
"fr": "De (%T1)",
|
||||
"id": "Dari (%T1)",
|
||||
"ru": "От (%T1)"
|
||||
},
|
||||
{
|
||||
"key": "暂无可用模板",
|
||||
"zh": "",
|
||||
"zh-CHT": "暫無可用範本",
|
||||
"en": "No templates available",
|
||||
"ko": "사용 가능한 템플릿이 없습니다",
|
||||
"ja": "利用可能なテンプレートはありません",
|
||||
"de": "Keine Vorlagen verfügbar",
|
||||
"fr": "Aucun modèle disponible",
|
||||
"id": "Belum ada templat yang tersedia",
|
||||
"ru": "Нет доступных шаблонов"
|
||||
},
|
||||
{
|
||||
"key": "加载中",
|
||||
"zh": "",
|
||||
"zh-CHT": "載入中",
|
||||
"en": "Loading",
|
||||
"ko": "로딩 중",
|
||||
"ja": "読み込み中",
|
||||
"de": "Wird geladen",
|
||||
"fr": "Chargement",
|
||||
"id": "Memuat",
|
||||
"ru": "Загрузка"
|
||||
},
|
||||
{
|
||||
"key": "共享模板",
|
||||
"zh": "",
|
||||
"zh-CHT": "共享範本",
|
||||
"en": "Shared templates",
|
||||
"ko": "공유 템플릿",
|
||||
"ja": "共有テンプレート",
|
||||
"de": "Geteilte Vorlagen",
|
||||
"fr": "Modèles partagés",
|
||||
"id": "Templat bersama",
|
||||
"ru": "Общие шаблоны"
|
||||
},
|
||||
{
|
||||
"key": "开启后,添加任务时可使用其他项目共享的任务模板。",
|
||||
"zh": "",
|
||||
"zh-CHT": "開啟後,新增任務時可使用其他專案共享的任務範本。",
|
||||
"en": "When enabled, task templates shared by other projects can be used when adding tasks.",
|
||||
"ko": "켜면 작업을 추가할 때 다른 프로젝트에서 공유한 작업 템플릿을 사용할 수 있습니다.",
|
||||
"ja": "有効にすると、タスク追加時に他のプロジェクトで共有されているタスクテンプレートを使用できます。",
|
||||
"de": "Wenn aktiviert, können beim Hinzufügen von Aufgaben von anderen Projekten geteilte Aufgabenvorlagen verwendet werden.",
|
||||
"fr": "Une fois activé, les modèles de tâches partagés par d’autres projets peuvent être utilisés lors de l’ajout de tâches.",
|
||||
"id": "Setelah diaktifkan, templat tugas yang dibagikan oleh proyek lain dapat digunakan saat menambahkan tugas.",
|
||||
"ru": "После включения при добавлении задач можно использовать шаблоны задач, которыми поделились другие проекты."
|
||||
},
|
||||
{
|
||||
"key": "关闭后,添加任务时仅加载本项目模板,不显示其他项目共享模板。",
|
||||
"zh": "",
|
||||
"zh-CHT": "關閉後,新增任務時僅載入本專案範本,不顯示其他專案共享範本。",
|
||||
"en": "When disabled, only templates from this project will be loaded when adding tasks, and templates shared by other projects will not be displayed.",
|
||||
"ko": "끄면 작업을 추가할 때 이 프로젝트의 템플릿만 로드되며, 다른 프로젝트에서 공유한 템플릿은 표시되지 않습니다.",
|
||||
"ja": "無効にすると、タスク追加時にはこのプロジェクトのテンプレートのみが読み込まれ、他のプロジェクトで共有されているテンプレートは表示されません。",
|
||||
"de": "Wenn deaktiviert, werden beim Hinzufügen von Aufgaben nur Vorlagen dieses Projekts geladen; von anderen Projekten geteilte Vorlagen werden nicht angezeigt.",
|
||||
"fr": "Une fois désactivé, seuls les modèles de ce projet seront chargés lors de l’ajout de tâches, et les modèles partagés par d’autres projets ne seront pas affichés.",
|
||||
"id": "Setelah dinonaktifkan, saat menambahkan tugas hanya templat proyek ini yang akan dimuat, dan templat yang dibagikan oleh proyek lain tidak akan ditampilkan.",
|
||||
"ru": "После отключения при добавлении задач будут загружаться только шаблоны этого проекта, а шаблоны, которыми поделились другие проекты, отображаться не будут."
|
||||
},
|
||||
{
|
||||
"key": "根据系统设置的自动归档规则执行",
|
||||
"zh": "",
|
||||
"zh-CHT": "根據系統設定的自動歸檔規則執行",
|
||||
"en": "Execute according to the auto-archiving rules set in the system",
|
||||
"ko": "시스템에 설정된 자동 보관 규칙에 따라 실행",
|
||||
"ja": "システムで設定された自動アーカイブルールに従って実行",
|
||||
"de": "Gemäß den im System festgelegten Regeln für die automatische Archivierung ausführen",
|
||||
"fr": "Exécuter selon les règles d’archivage automatique définies dans le système",
|
||||
"id": "Jalankan sesuai aturan pengarsipan otomatis yang ditetapkan dalam sistem",
|
||||
"ru": "Выполнять в соответствии с правилами автоматического архивирования, заданными в системе"
|
||||
},
|
||||
{
|
||||
"key": "负责人视角",
|
||||
"zh": "",
|
||||
"zh-CHT": "負責人視角",
|
||||
"en": "Manager view",
|
||||
"ko": "담당자 보기",
|
||||
"ja": "責任者ビュー",
|
||||
"de": "Verantwortlichenansicht",
|
||||
"fr": "Vue du responsable",
|
||||
"id": "Tampilan penanggung jawab",
|
||||
"ru": "Представление ответственного"
|
||||
},
|
||||
{
|
||||
"key": "开启后,部门负责人可只读查看本项目及其全员可见任务。",
|
||||
"zh": "",
|
||||
"zh-CHT": "開啟後,部門負責人可唯讀檢視本專案及其全員可見任務。",
|
||||
"en": "After enabled, department managers can view this project and its tasks visible to all members in read-only mode.",
|
||||
"ko": "활성화하면 부서 책임자가 이 프로젝트 및 전체 구성원에게 표시되는 작업을 읽기 전용으로 볼 수 있습니다.",
|
||||
"ja": "有効にすると、部門責任者はこのプロジェクトおよび全員に表示されるタスクを読み取り専用で閲覧できます。",
|
||||
"de": "Nach der Aktivierung können Abteilungsleiter dieses Projekt und die für alle Mitglieder sichtbaren Aufgaben schreibgeschützt anzeigen.",
|
||||
"fr": "Une fois activé, les responsables de département peuvent consulter ce projet et ses tâches visibles par tous les membres en lecture seule.",
|
||||
"id": "Setelah diaktifkan, kepala departemen dapat melihat proyek ini dan tugas yang terlihat oleh semua anggota dalam mode hanya-baca.",
|
||||
"ru": "После включения руководители отделов смогут просматривать этот проект и его задачи, видимые всем участникам, в режиме только для чтения."
|
||||
},
|
||||
{
|
||||
"key": "关闭后,本项目及其群聊对部门负责人视角隐藏。",
|
||||
"zh": "",
|
||||
"zh-CHT": "關閉後,本專案及其群聊會在部門負責人視角中隱藏。",
|
||||
"en": "After disabled, this project and its group chat will be hidden from the department manager view.",
|
||||
"ko": "비활성화하면 이 프로젝트와 해당 그룹 채팅이 부서 책임자 보기에서 숨겨집니다.",
|
||||
"ja": "無効にすると、このプロジェクトおよびそのグループチャットは部門責任者ビューで非表示になります。",
|
||||
"de": "Nach der Deaktivierung werden dieses Projekt und sein Gruppenchat in der Abteilungsleiteransicht ausgeblendet.",
|
||||
"fr": "Une fois désactivé, ce projet et sa discussion de groupe seront masqués dans la vue du responsable de département.",
|
||||
"id": "Setelah dinonaktifkan, proyek ini dan obrolan grupnya akan disembunyikan dari tampilan kepala departemen.",
|
||||
"ru": "После отключения этот проект и его групповой чат будут скрыты в представлении руководителя отдела."
|
||||
},
|
||||
{
|
||||
"key": "个人项目,只读查看",
|
||||
"zh": "",
|
||||
"zh-CHT": "個人專案,唯讀檢視",
|
||||
"en": "Personal project, read-only view",
|
||||
"ko": "개인 프로젝트, 읽기 전용 보기",
|
||||
"ja": "個人プロジェクト、読み取り専用表示",
|
||||
"de": "Persönliches Projekt, schreibgeschützte Ansicht",
|
||||
"fr": "Projet personnel, consultation en lecture seule",
|
||||
"id": "Proyek pribadi, tampilan hanya-baca",
|
||||
"ru": "Личный проект, просмотр только для чтения"
|
||||
},
|
||||
{
|
||||
"key": "负责人视角,只读查看",
|
||||
"zh": "",
|
||||
"zh-CHT": "負責人視角,唯讀檢視",
|
||||
"en": "Manager view, read-only",
|
||||
"ko": "담당자 보기, 읽기 전용",
|
||||
"ja": "責任者ビュー、読み取り専用",
|
||||
"de": "Verantwortlichenansicht, schreibgeschützt",
|
||||
"fr": "Vue du responsable, lecture seule",
|
||||
"id": "Tampilan penanggung jawab, hanya-baca",
|
||||
"ru": "Представление ответственного, только для чтения"
|
||||
},
|
||||
{
|
||||
"key": "我的项目",
|
||||
"zh": "",
|
||||
"zh-CHT": "我的專案",
|
||||
"en": "My Projects",
|
||||
"ko": "내 프로젝트",
|
||||
"ja": "マイプロジェクト",
|
||||
"de": "Meine Projekte",
|
||||
"fr": "Mes projets",
|
||||
"id": "Proyek Saya",
|
||||
"ru": "Мои проекты"
|
||||
},
|
||||
{
|
||||
"key": "没有任何与\"(%T1)\"相关的结果",
|
||||
"zh": "",
|
||||
"zh-CHT": "沒有任何與「(%T1)」相關的結果",
|
||||
"en": "No results related to \"(%T1)\"",
|
||||
"ko": "\"(%T1)\"와(과) 관련된 결과가 없습니다",
|
||||
"ja": "「(%T1)」に関連する結果はありません",
|
||||
"de": "Keine Ergebnisse im Zusammenhang mit „(%T1)“",
|
||||
"fr": "Aucun résultat lié à « (%T1) »",
|
||||
"id": "Tidak ada hasil yang terkait dengan \"(%T1)\"",
|
||||
"ru": "Нет результатов, связанных с «(%T1)»"
|
||||
},
|
||||
{
|
||||
"key": "标记未选",
|
||||
"zh": "",
|
||||
"zh-CHT": "標記未選",
|
||||
"en": "Mark as unselected",
|
||||
"ko": "선택되지 않음으로 표시",
|
||||
"ja": "未選択としてマーク",
|
||||
"de": "Als nicht ausgewählt markieren",
|
||||
"fr": "Marquer comme non sélectionné",
|
||||
"id": "Tandai sebagai belum dipilih",
|
||||
"ru": "Отметить как не выбранное"
|
||||
},
|
||||
{
|
||||
"key": "标记已选",
|
||||
"zh": "",
|
||||
"zh-CHT": "標記已選",
|
||||
"en": "Mark as selected",
|
||||
"ko": "선택됨으로 표시",
|
||||
"ja": "選択済みとしてマーク",
|
||||
"de": "Als ausgewählt markieren",
|
||||
"fr": "Marquer comme sélectionné",
|
||||
"id": "Tandai sebagai dipilih",
|
||||
"ru": "Отметить как выбранное"
|
||||
},
|
||||
{
|
||||
"key": "可查看所选部门及所有下级部门成员参与的项目和任务,仅支持只读查看。",
|
||||
"zh": "",
|
||||
"zh-CHT": "可查看所選部門及所有下級部門成員參與的專案和任務,僅支援唯讀查看。",
|
||||
"en": "You can view projects and tasks participated in by members of the selected department and all subordinate departments. Read-only viewing only.",
|
||||
"ko": "선택한 부서 및 모든 하위 부서 구성원이 참여한 프로젝트와 작업을 볼 수 있으며, 읽기 전용 보기만 지원됩니다.",
|
||||
"ja": "選択した部門およびすべての下位部門のメンバーが参加しているプロジェクトとタスクを表示できます。読み取り専用表示のみ対応しています。",
|
||||
"de": "Sie können Projekte und Aufgaben anzeigen, an denen Mitglieder der ausgewählten Abteilung und aller untergeordneten Abteilungen beteiligt sind. Nur schreibgeschützte Ansicht wird unterstützt.",
|
||||
"fr": "Vous pouvez afficher les projets et les tâches auxquels participent les membres du département sélectionné et de tous les départements subordonnés. Affichage en lecture seule uniquement.",
|
||||
"id": "Anda dapat melihat proyek dan tugas yang diikuti oleh anggota departemen yang dipilih serta semua departemen bawahannya. Hanya mendukung tampilan baca-saja.",
|
||||
"ru": "Вы можете просматривать проекты и задачи, в которых участвуют сотрудники выбранного отдела и всех подчиненных отделов. Доступен только просмотр без права редактирования."
|
||||
},
|
||||
{
|
||||
"key": "反选",
|
||||
"zh": "",
|
||||
"zh-CHT": "反選",
|
||||
"en": "Invert Selection",
|
||||
"ko": "선택 반전",
|
||||
"ja": "選択を反転",
|
||||
"de": "Auswahl umkehren",
|
||||
"fr": "Inverser la sélection",
|
||||
"id": "Balikkan Pilihan",
|
||||
"ru": "Инвертировать выбор"
|
||||
},
|
||||
{
|
||||
"key": "切换失败",
|
||||
"zh": "",
|
||||
"zh-CHT": "切換失敗",
|
||||
"en": "Failed to switch",
|
||||
"ko": "전환 실패",
|
||||
"ja": "切り替えに失敗しました",
|
||||
"de": "Wechsel fehlgeschlagen",
|
||||
"fr": "Échec du changement",
|
||||
"id": "Gagal beralih",
|
||||
"ru": "Не удалось переключить"
|
||||
},
|
||||
{
|
||||
"key": "当前为负责人视角:你可查看项目和任务,并参与讨论,但不能编辑项目或任务。",
|
||||
"zh": "",
|
||||
"zh-CHT": "目前為負責人視角:你可查看專案和任務,並參與討論,但不能編輯專案或任務。",
|
||||
"en": "Currently in manager view: You can view projects and tasks and participate in discussions, but cannot edit projects or tasks.",
|
||||
"ko": "현재 책임자 관점입니다: 프로젝트와 작업을 보고 토론에 참여할 수 있지만, 프로젝트나 작업을 편집할 수는 없습니다.",
|
||||
"ja": "現在は責任者ビューです:プロジェクトとタスクを表示し、ディスカッションに参加できますが、プロジェクトやタスクを編集することはできません。",
|
||||
"de": "Derzeit in Verantwortlichenansicht: Sie können Projekte und Aufgaben anzeigen und an Diskussionen teilnehmen, aber keine Projekte oder Aufgaben bearbeiten.",
|
||||
"fr": "Vue responsable actuellement active : vous pouvez afficher les projets et les tâches et participer aux discussions, mais vous ne pouvez pas modifier les projets ou les tâches.",
|
||||
"id": "Saat ini dalam tampilan penanggung jawab: Anda dapat melihat proyek dan tugas serta berpartisipasi dalam diskusi, tetapi tidak dapat mengedit proyek atau tugas.",
|
||||
"ru": "Текущий режим — вид ответственного: вы можете просматривать проекты и задачи и участвовать в обсуждениях, но не можете редактировать проекты или задачи."
|
||||
},
|
||||
{
|
||||
"key": "选择项目管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "選擇專案管理員",
|
||||
"en": "Select Project Administrator",
|
||||
"ko": "프로젝트 관리자 선택",
|
||||
"ja": "プロジェクト管理者を選択",
|
||||
"de": "Projektadministrator auswählen",
|
||||
"fr": "Sélectionner un administrateur de projet",
|
||||
"id": "Pilih Administrator Proyek",
|
||||
"ru": "Выбрать администратора проекта"
|
||||
},
|
||||
{
|
||||
"key": "即将移除",
|
||||
"zh": "",
|
||||
"zh-CHT": "即將移除",
|
||||
"en": "About to be removed",
|
||||
"ko": "곧 제거됨",
|
||||
"ja": "まもなく削除されます",
|
||||
"de": "Wird demnächst entfernt",
|
||||
"fr": "Sur le point d’être supprimé",
|
||||
"id": "Akan segera dihapus",
|
||||
"ru": "Будет удалено"
|
||||
},
|
||||
{
|
||||
"key": "当前为负责人,并参与讨论,但不能编辑任务。",
|
||||
"zh": "",
|
||||
"zh-CHT": "目前為負責人,並參與討論,但不能編輯任務。",
|
||||
"en": "Currently a manager and participates in discussions, but cannot edit tasks.",
|
||||
"ko": "현재 책임자이며 토론에 참여하지만, 작업을 편집할 수는 없습니다.",
|
||||
"ja": "現在は責任者としてディスカッションに参加できますが、タスクを編集することはできません。",
|
||||
"de": "Derzeit Verantwortlicher und an Diskussionen beteiligt, kann jedoch keine Aufgaben bearbeiten.",
|
||||
"fr": "Actuellement responsable et participant aux discussions, mais ne peut pas modifier les tâches.",
|
||||
"id": "Saat ini sebagai penanggung jawab dan berpartisipasi dalam diskusi, tetapi tidak dapat mengedit tugas.",
|
||||
"ru": "В настоящее время является ответственным и участвует в обсуждениях, но не может редактировать задачи."
|
||||
},
|
||||
{
|
||||
"key": "部门负责人视角",
|
||||
"zh": "",
|
||||
"zh-CHT": "部門負責人視角",
|
||||
"en": "Department Manager View",
|
||||
"ko": "부서 책임자 관점",
|
||||
"ja": "部門責任者ビュー",
|
||||
"de": "Abteilungsleiteransicht",
|
||||
"fr": "Vue responsable de département",
|
||||
"id": "Tampilan Penanggung Jawab Departemen",
|
||||
"ru": "Вид руководителя отдела"
|
||||
},
|
||||
{
|
||||
"key": "开启后,部门负责人\/部门管理员可只读查看本部门及下级部门成员参与的项目和项目内全部任务。",
|
||||
"zh": "",
|
||||
"zh-CHT": "開啟後,部門負責人\/部門管理員可唯讀查看本部門及下級部門成員參與的專案和專案內全部任務。",
|
||||
"en": "After enabled, department managers\/department administrators can view, in read-only mode, projects participated in by members of their department and subordinate departments, as well as all tasks within those projects.",
|
||||
"ko": "활성화하면 부서 책임자\/부서 관리자가 해당 부서 및 하위 부서 구성원이 참여한 프로젝트와 프로젝트 내 모든 작업을 읽기 전용으로 볼 수 있습니다.",
|
||||
"ja": "有効にすると、部門責任者/部門管理者は、自部門および下位部門のメンバーが参加しているプロジェクトと、そのプロジェクト内のすべてのタスクを読み取り専用で表示できます。",
|
||||
"de": "Nach der Aktivierung können Abteilungsleiter\/Abteilungsadministratoren Projekte, an denen Mitglieder ihrer Abteilung und untergeordneter Abteilungen beteiligt sind, sowie alle Aufgaben in diesen Projekten schreibgeschützt anzeigen.",
|
||||
"fr": "Une fois activé, les responsables\/administrateurs de département peuvent afficher en lecture seule les projets auxquels participent les membres de leur département et des départements subordonnés, ainsi que toutes les tâches de ces projets.",
|
||||
"id": "Setelah diaktifkan, penanggung jawab departemen\/administrator departemen dapat melihat secara baca-saja proyek yang diikuti oleh anggota departemennya dan departemen bawahan, serta semua tugas dalam proyek tersebut.",
|
||||
"ru": "После включения руководители\/администраторы отдела смогут в режиме только для чтения просматривать проекты, в которых участвуют сотрудники их отдела и подчиненных отделов, а также все задачи внутри этих проектов."
|
||||
},
|
||||
{
|
||||
"key": "部门管理员同步失败",
|
||||
"zh": "",
|
||||
"zh-CHT": "部門管理員同步失敗",
|
||||
"en": "Failed to sync department administrators",
|
||||
"ko": "부서 관리자 동기화 실패",
|
||||
"ja": "部門管理者の同期に失敗しました",
|
||||
"de": "Synchronisierung der Abteilungsadministratoren fehlgeschlagen",
|
||||
"fr": "Échec de la synchronisation des administrateurs de département",
|
||||
"id": "Gagal menyinkronkan administrator departemen",
|
||||
"ru": "Не удалось синхронизировать администраторов отдела"
|
||||
},
|
||||
{
|
||||
"key": "该用户不是群成员",
|
||||
"zh": "",
|
||||
"zh-CHT": "該使用者不是群組成員",
|
||||
"en": "This user is not a group member",
|
||||
"ko": "해당 사용자는 그룹 멤버가 아닙니다",
|
||||
"ja": "このユーザーはグループメンバーではありません",
|
||||
"de": "Dieser Benutzer ist kein Gruppenmitglied",
|
||||
"fr": "Cet utilisateur n’est pas membre du groupe",
|
||||
"id": "Pengguna ini bukan anggota grup",
|
||||
"ru": "Этот пользователь не является участником группы"
|
||||
},
|
||||
{
|
||||
"key": "不能将群主任命为群管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "不能將群主任命為群組管理員",
|
||||
"en": "The group owner cannot be appointed as a group administrator",
|
||||
"ko": "그룹 소유자는 그룹 관리자로 임명할 수 없습니다",
|
||||
"ja": "グループオーナーをグループ管理者に任命することはできません",
|
||||
"de": "Der Gruppeneigentümer kann nicht zum Gruppenadministrator ernannt werden",
|
||||
"fr": "Le propriétaire du groupe ne peut pas être nommé administrateur du groupe",
|
||||
"id": "Pemilik grup tidak dapat ditunjuk sebagai administrator grup",
|
||||
"ru": "Владельца группы нельзя назначить администратором группы"
|
||||
},
|
||||
{
|
||||
"key": "仅群主或群管理员可操作",
|
||||
"zh": "",
|
||||
"zh-CHT": "僅群主或群組管理員可操作",
|
||||
"en": "Only the group owner or group administrators can perform this action",
|
||||
"ko": "그룹 소유자 또는 그룹 관리자만 이 작업을 수행할 수 있습니다",
|
||||
"ja": "グループオーナーまたはグループ管理者のみ操作できます",
|
||||
"de": "Nur der Gruppeneigentümer oder Gruppenadministratoren können diese Aktion ausführen",
|
||||
"fr": "Seul le propriétaire du groupe ou les administrateurs du groupe peuvent effectuer cette action",
|
||||
"id": "Hanya pemilik grup atau administrator grup yang dapat melakukan tindakan ini",
|
||||
"ru": "Только владелец группы или администраторы группы могут выполнить это действие"
|
||||
},
|
||||
{
|
||||
"key": "仅限群主或群管理员操作",
|
||||
"zh": "",
|
||||
"zh-CHT": "僅限群主或群組管理員操作",
|
||||
"en": "This action is limited to the group owner or group administrators",
|
||||
"ko": "이 작업은 그룹 소유자 또는 그룹 관리자만 수행할 수 있습니다",
|
||||
"ja": "この操作はグループオーナーまたはグループ管理者に限定されています",
|
||||
"de": "Diese Aktion ist auf den Gruppeneigentümer oder Gruppenadministratoren beschränkt",
|
||||
"fr": "Cette action est réservée au propriétaire du groupe ou aux administrateurs du groupe",
|
||||
"id": "Tindakan ini hanya dapat dilakukan oleh pemilik grup atau administrator grup",
|
||||
"ru": "Это действие доступно только владельцу группы или администраторам группы"
|
||||
},
|
||||
{
|
||||
"key": "群管理员不能移出群主或其他群管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "群組管理員不能移出群主或其他群組管理員",
|
||||
"en": "Group administrators cannot remove the group owner or other group administrators",
|
||||
"ko": "그룹 관리자는 그룹 소유자 또는 다른 그룹 관리자를 내보낼 수 없습니다",
|
||||
"ja": "グループ管理者はグループオーナーまたは他のグループ管理者を削除できません",
|
||||
"de": "Gruppenadministratoren können den Gruppeneigentümer oder andere Gruppenadministratoren nicht entfernen",
|
||||
"fr": "Les administrateurs du groupe ne peuvent pas retirer le propriétaire du groupe ni d’autres administrateurs du groupe",
|
||||
"id": "Administrator grup tidak dapat mengeluarkan pemilik grup atau administrator grup lainnya",
|
||||
"ru": "Администраторы группы не могут удалить владельца группы или других администраторов группы"
|
||||
},
|
||||
{
|
||||
"key": "请选择有效的成员",
|
||||
"zh": "",
|
||||
"zh-CHT": "請選擇有效的成員",
|
||||
"en": "Please select a valid member",
|
||||
"ko": "유효한 멤버를 선택하세요",
|
||||
"ja": "有効なメンバーを選択してください",
|
||||
"de": "Bitte wählen Sie ein gültiges Mitglied aus",
|
||||
"fr": "Veuillez sélectionner un membre valide",
|
||||
"id": "Silakan pilih anggota yang valid",
|
||||
"ru": "Выберите действительного участника"
|
||||
},
|
||||
{
|
||||
"key": "任命成功",
|
||||
"zh": "",
|
||||
"zh-CHT": "任命成功",
|
||||
"en": "Appointment successful",
|
||||
"ko": "임명되었습니다",
|
||||
"ja": "任命に成功しました",
|
||||
"de": "Ernennung erfolgreich",
|
||||
"fr": "Nomination réussie",
|
||||
"id": "Penunjukan berhasil",
|
||||
"ru": "Назначение выполнено успешно"
|
||||
},
|
||||
{
|
||||
"key": "罢免成功",
|
||||
"zh": "",
|
||||
"zh-CHT": "罷免成功",
|
||||
"en": "Removal successful",
|
||||
"ko": "해임되었습니다",
|
||||
"ja": "解任に成功しました",
|
||||
"de": "Abberufung erfolgreich",
|
||||
"fr": "Révocation réussie",
|
||||
"id": "Pemberhentian berhasil",
|
||||
"ru": "Снятие с должности выполнено успешно"
|
||||
},
|
||||
{
|
||||
"key": "该用户不是项目成员",
|
||||
"zh": "",
|
||||
"zh-CHT": "該使用者不是專案成員",
|
||||
"en": "This user is not a project member",
|
||||
"ko": "해당 사용자는 프로젝트 멤버가 아닙니다",
|
||||
"ja": "このユーザーはプロジェクトメンバーではありません",
|
||||
"de": "Dieser Benutzer ist kein Projektmitglied",
|
||||
"fr": "Cet utilisateur n’est pas membre du projet",
|
||||
"id": "Pengguna ini bukan anggota proyek",
|
||||
"ru": "Этот пользователь не является участником проекта"
|
||||
},
|
||||
{
|
||||
"key": "不能将负责人任命为项目管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "不能將負責人任命為專案管理員",
|
||||
"en": "The person in charge cannot be appointed as a project administrator",
|
||||
"ko": "책임자를 프로젝트 관리자로 임명할 수 없습니다",
|
||||
"ja": "担当責任者をプロジェクト管理者に任命することはできません",
|
||||
"de": "Die verantwortliche Person kann nicht zum Projektadministrator ernannt werden",
|
||||
"fr": "La personne responsable ne peut pas être nommée administrateur du projet",
|
||||
"id": "Penanggung jawab tidak dapat ditunjuk sebagai administrator proyek",
|
||||
"ru": "Ответственного нельзя назначить администратором проекта"
|
||||
},
|
||||
{
|
||||
"key": "不能将部门负责人任命为部门管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "不能將部門負責人任命為部門管理員",
|
||||
"en": "The department owner cannot be appointed as the department administrator",
|
||||
"ko": "부서 책임자를 부서 관리자로 임명할 수 없습니다",
|
||||
"ja": "部門責任者を部門管理者に任命することはできません",
|
||||
"de": "Der Abteilungsverantwortliche kann nicht zum Abteilungsadministrator ernannt werden",
|
||||
"fr": "Le responsable du service ne peut pas être nommé administrateur du service",
|
||||
"id": "Penanggung jawab departemen tidak dapat ditunjuk sebagai administrator departemen",
|
||||
"ru": "Ответственного за отдел нельзя назначить администратором отдела"
|
||||
},
|
||||
{
|
||||
"key": "该用户不存在",
|
||||
"zh": "",
|
||||
"zh-CHT": "該使用者不存在",
|
||||
"en": "This user does not exist",
|
||||
"ko": "해당 사용자가 존재하지 않습니다",
|
||||
"ja": "このユーザーは存在しません",
|
||||
"de": "Dieser Benutzer existiert nicht",
|
||||
"fr": "Cet utilisateur n’existe pas",
|
||||
"id": "Pengguna ini tidak ada",
|
||||
"ru": "Этот пользователь не существует"
|
||||
},
|
||||
{
|
||||
"key": "无权操作此模板",
|
||||
"zh": "",
|
||||
"zh-CHT": "無權操作此範本",
|
||||
"en": "You do not have permission to operate this template",
|
||||
"ko": "이 템플릿을 조작할 권한이 없습니다",
|
||||
"ja": "このテンプレートを操作する権限がありません",
|
||||
"de": "Sie haben keine Berechtigung, diese Vorlage zu bearbeiten",
|
||||
"fr": "Vous n’avez pas l’autorisation d’utiliser ce modèle",
|
||||
"id": "Anda tidak memiliki izin untuk mengoperasikan templat ini",
|
||||
"ru": "У вас нет прав на работу с этим шаблоном"
|
||||
},
|
||||
{
|
||||
"key": "修改共享模板",
|
||||
"zh": "",
|
||||
"zh-CHT": "修改共用範本",
|
||||
"en": "Modify shared template",
|
||||
"ko": "공유 템플릿 수정",
|
||||
"ja": "共有テンプレートを変更",
|
||||
"de": "Freigegebene Vorlage ändern",
|
||||
"fr": "Modifier le modèle partagé",
|
||||
"id": "Ubah templat bersama",
|
||||
"ru": "Изменить общий шаблон"
|
||||
},
|
||||
{
|
||||
"key": "修改负责人视角可见",
|
||||
"zh": "",
|
||||
"zh-CHT": "修改負責人視角可見",
|
||||
"en": "Modify visibility in owner view",
|
||||
"ko": "책임자 보기에서 표시 여부 수정",
|
||||
"ja": "責任者ビューでの表示設定を変更",
|
||||
"de": "Sichtbarkeit in der Verantwortlichenansicht ändern",
|
||||
"fr": "Modifier la visibilité dans la vue du responsable",
|
||||
"id": "Ubah visibilitas dalam tampilan penanggung jawab",
|
||||
"ru": "Изменить видимость в представлении ответственного"
|
||||
},
|
||||
{
|
||||
"key": "项目负责人数据异常,请先修复项目负责人",
|
||||
"zh": "",
|
||||
"zh-CHT": "專案負責人資料異常,請先修復專案負責人",
|
||||
"en": "Project owner data is abnormal. Please fix the project owner first",
|
||||
"ko": "프로젝트 책임자 데이터에 이상이 있습니다. 먼저 프로젝트 책임자를 수정하세요",
|
||||
"ja": "プロジェクト責任者のデータに異常があります。先にプロジェクト責任者を修正してください",
|
||||
"de": "Die Daten des Projektverantwortlichen sind fehlerhaft. Bitte beheben Sie zuerst den Projektverantwortlichen",
|
||||
"fr": "Les données du responsable du projet sont anormales. Veuillez d’abord corriger le responsable du projet",
|
||||
"id": "Data penanggung jawab proyek tidak normal. Harap perbaiki penanggung jawab proyek terlebih dahulu",
|
||||
"ru": "Данные ответственного за проект некорректны. Сначала исправьте ответственного за проект"
|
||||
},
|
||||
{
|
||||
"key": "项目成员列表必须包含项目负责人",
|
||||
"zh": "",
|
||||
"zh-CHT": "專案成員清單必須包含專案負責人",
|
||||
"en": "The project member list must include the project owner",
|
||||
"ko": "프로젝트 구성원 목록에는 프로젝트 책임자가 포함되어야 합니다",
|
||||
"ja": "プロジェクトメンバーリストにはプロジェクト責任者を含める必要があります",
|
||||
"de": "Die Projektmitgliederliste muss den Projektverantwortlichen enthalten",
|
||||
"fr": "La liste des membres du projet doit inclure le responsable du projet",
|
||||
"id": "Daftar anggota proyek harus mencakup penanggung jawab proyek",
|
||||
"ru": "Список участников проекта должен включать ответственного за проект"
|
||||
},
|
||||
{
|
||||
"key": "项目管理员不能移除项目负责人或项目管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "專案管理員不能移除專案負責人或專案管理員",
|
||||
"en": "Project administrators cannot remove the project owner or project administrators",
|
||||
"ko": "프로젝트 관리자는 프로젝트 책임자 또는 프로젝트 관리자를 제거할 수 없습니다",
|
||||
"ja": "プロジェクト管理者はプロジェクト責任者またはプロジェクト管理者を削除できません",
|
||||
"de": "Projektadministratoren können den Projektverantwortlichen oder Projektadministratoren nicht entfernen",
|
||||
"fr": "Les administrateurs du projet ne peuvent pas supprimer le responsable du projet ni les administrateurs du projet",
|
||||
"id": "Administrator proyek tidak dapat menghapus penanggung jawab proyek atau administrator proyek",
|
||||
"ru": "Администраторы проекта не могут удалить ответственного за проект или администраторов проекта"
|
||||
},
|
||||
{
|
||||
"key": "项目管理员必须是项目成员",
|
||||
"zh": "",
|
||||
"zh-CHT": "專案管理員必須是專案成員",
|
||||
"en": "Project administrators must be project members",
|
||||
"ko": "프로젝트 관리자는 프로젝트 구성원이어야 합니다",
|
||||
"ja": "プロジェクト管理者はプロジェクトメンバーである必要があります",
|
||||
"de": "Projektadministratoren müssen Projektmitglieder sein",
|
||||
"fr": "Les administrateurs du projet doivent être membres du projet",
|
||||
"id": "Administrator proyek harus merupakan anggota proyek",
|
||||
"ru": "Администраторы проекта должны быть участниками проекта"
|
||||
},
|
||||
{
|
||||
"key": "负责人不能任命为项目管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "負責人不能任命為專案管理員",
|
||||
"en": "The owner cannot be appointed as a project administrator",
|
||||
"ko": "책임자는 프로젝트 관리자로 임명할 수 없습니다",
|
||||
"ja": "責任者をプロジェクト管理者に任命することはできません",
|
||||
"de": "Der Verantwortliche kann nicht zum Projektadministrator ernannt werden",
|
||||
"fr": "Le responsable ne peut pas être nommé administrateur du projet",
|
||||
"id": "Penanggung jawab tidak dapat ditunjuk sebagai administrator proyek",
|
||||
"ru": "Ответственного нельзя назначить администратором проекта"
|
||||
},
|
||||
{
|
||||
"key": "普通成员不能移出群主或群管理员",
|
||||
"zh": "",
|
||||
"zh-CHT": "普通成員不能移出群主或群管理員",
|
||||
"en": "Regular members cannot remove the group owner or group administrators",
|
||||
"ko": "일반 멤버는 그룹 소유자 또는 그룹 관리자를 내보낼 수 없습니다",
|
||||
"ja": "一般メンバーはグループオーナーまたはグループ管理者を削除できません",
|
||||
"de": "Normale Mitglieder können den Gruppeninhaber oder Gruppenadministratoren nicht entfernen",
|
||||
"fr": "Les membres ordinaires ne peuvent pas retirer le propriétaire du groupe ni les administrateurs du groupe",
|
||||
"id": "Anggota biasa tidak dapat mengeluarkan pemilik grup atau administrator grup",
|
||||
"ru": "Обычные участники не могут удалить владельца группы или администраторов группы"
|
||||
},
|
||||
{
|
||||
"key": "只有群主、群管理员或邀请人可以移出成员",
|
||||
"zh": "",
|
||||
"zh-CHT": "只有群主、群管理員或邀請人可以移出成員",
|
||||
"en": "Only the group owner, group administrators, or the inviter can remove members",
|
||||
"ko": "그룹 소유자, 그룹 관리자 또는 초대한 사람만 멤버를 내보낼 수 있습니다",
|
||||
"ja": "グループオーナー、グループ管理者、または招待者のみがメンバーを削除できます",
|
||||
"de": "Nur der Gruppeninhaber, Gruppenadministratoren oder die einladende Person können Mitglieder entfernen",
|
||||
"fr": "Seuls le propriétaire du groupe, les administrateurs du groupe ou l’invitant peuvent retirer des membres",
|
||||
"id": "Hanya pemilik grup, administrator grup, atau pengundang yang dapat mengeluarkan anggota",
|
||||
"ru": "Только владелец группы, администраторы группы или пригласивший пользователь могут удалять участников"
|
||||
}
|
||||
]
|
||||
@ -76,6 +76,7 @@ if ($openAiKey === '') {
|
||||
}
|
||||
$openAiProxy = trim(language_env_value('OPENAI_PROXY_URL', $languageEnv) ?? '');
|
||||
$openAiBaseUrl = trim(language_env_value('OPENAI_BASE_URL', $languageEnv) ?? '');
|
||||
$openAiModel = trim(language_env_value('OPENAI_API_MODEL', $languageEnv) ?? '');
|
||||
|
||||
// 读取所有要翻译的内容
|
||||
$originals = [];
|
||||
@ -178,7 +179,7 @@ if (count($needs) > 0) {
|
||||
$openAi->setProxy($openAiProxy);
|
||||
}
|
||||
$result = $openAi->chat([
|
||||
"model" => "gpt-5.2",
|
||||
"model" => $openAiModel,
|
||||
"reasoning_effort" => "low",
|
||||
'messages' => [
|
||||
[
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "DooTask",
|
||||
"version": "1.7.29",
|
||||
"codeVerson": 232,
|
||||
"version": "1.7.55",
|
||||
"codeVerson": 233,
|
||||
"description": "DooTask is task management system.",
|
||||
"scripts": {
|
||||
"start": "./cmd dev",
|
||||
|
||||
@ -1 +1 @@
|
||||
import{n as m}from"./app.20ce4f8e.js";import"./jquery.26755d2b.js";import"./@babel.9410f858.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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.f1128b4d.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.155cfddd.js";import"./jquery.b8b91492.js";import"./@babel.9410f858.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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.f1128b4d.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
1
public/js/build/CheckinExport.0f801a2c.js
vendored
Normal file
1
public/js/build/CheckinExport.0f801a2c.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/build/CheckinExport.fde214f8.js
vendored
1
public/js/build/CheckinExport.fde214f8.js
vendored
File diff suppressed because one or more lines are too long
1
public/js/build/DepartmentOwnerView.1f8e6bb3.js
vendored
Normal file
1
public/js/build/DepartmentOwnerView.1f8e6bb3.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{m as i}from"./vuex.cc7cb26e.js";import{n as o}from"./app.155cfddd.js";var d=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("Modal",{attrs:{value:t.value,title:t.$L("\u8D1F\u8D23\u4EBA\u89C6\u89D2"),"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._v(" "+t._s(t.$L("\u53EF\u67E5\u770B\u6240\u9009\u90E8\u95E8\u53CA\u6240\u6709\u4E0B\u7EA7\u90E8\u95E8\u6210\u5458\u53C2\u4E0E\u7684\u9879\u76EE\u548C\u4EFB\u52A1\uFF0C\u4EC5\u652F\u6301\u53EA\u8BFB\u67E5\u770B\u3002"))+" ")]),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)],1),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",loading:t.applyLoading},on:{click:t.apply}},[t._v(t._s(t.$L("\u786E\u5B9A")))])],1)])},l=[];const c={name:"DepartmentOwnerView",props:{value:Boolean},data(){return{draftIds:[],applyLoading:!1}},computed:{...i(["userInfo","cacheDepartmentOwnerIds"]),managedDepartments(){return(this.userInfo.managed_departments||[]).map(t=>({...t,id:parseInt(t.id)}))}},watch:{value:{immediate:!0,handler(t){t?this.draftIds=(this.cacheDepartmentOwnerIds||[]).slice():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.draftIds),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=o(c,d,l,!1,f,"624ab3e4",null,null);function f(t){for(let a in r)this[a]=r[a]}var u=function(){return p.exports}();export{u as D};
|
||||
1
public/js/build/DepartmentOwnerView.ecabb08d.css
vendored
Normal file
1
public/js/build/DepartmentOwnerView.ecabb08d.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.department-owner-view-modal .department-owner-view-actions[data-v-624ab3e4]{display:flex;justify-content:flex-end;gap:14px;margin:12px 8px 0}.department-owner-view-modal .department-owner-view-list[data-v-624ab3e4]{display:flex;flex-direction:column;margin-top:10px}.department-owner-view-modal .department-owner-view-item[data-v-624ab3e4]{display:flex;align-items:center;padding:10px 12px;cursor:pointer}.department-owner-view-modal .department-owner-view-icon[data-v-624ab3e4]{width:28px;height:28px;border-radius:50%;background-color:#5bc7b0;color:#fff;display:flex;align-items:center;justify-content:center;margin-right:10px}.department-owner-view-modal .department-owner-view-name[data-v-624ab3e4]{flex:1}.department-owner-view-modal .department-owner-view-checkbox[data-v-624ab3e4]{margin-right:0}
|
||||
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.b849e339.js";import{n as p,l as o}from"./app.20ce4f8e.js";import"./jquery.26755d2b.js";import"./@babel.9410f858.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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.f1128b4d.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,"39021859",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.a92ca567.js";import{n as p,l as o}from"./app.155cfddd.js";import"./jquery.b8b91492.js";import"./@babel.9410f858.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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.f1128b4d.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,"39021859",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.20ce4f8e.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.155cfddd.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.20ce4f8e.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.155cfddd.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.555a57e2.js
vendored
Normal file
1
public/js/build/TaskDetail.555a57e2.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/build/TaskDetail.60331a91.js
vendored
1
public/js/build/TaskDetail.60331a91.js
vendored
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
.task-editor[data-v-5adf557a]{position:relative;word-break:break-all}.task-editor[data-v-5adf557a] .mce-content-body,.task-editor[data-v-5adf557a] .task-editor-content{line-height:1.6}.task-editor[data-v-5adf557a] p{margin:.3em 0}.task-editor[data-v-5adf557a] blockquote,.task-editor[data-v-5adf557a] pre,.task-editor[data-v-5adf557a] ul,.task-editor[data-v-5adf557a] ol{margin:1em 0}.task-editor[data-v-5adf557a] ul,.task-editor[data-v-5adf557a] ol{margin-left:1.5em;padding-left:1.5em}.task-editor[data-v-5adf557a] li{margin:.25em 0}.task-editor[data-v-5adf557a] h1{margin:.67em 0}.task-editor[data-v-5adf557a] h2{margin:.83em 0}.task-editor[data-v-5adf557a] h3{margin:1em 0}.task-editor[data-v-5adf557a] h4{margin:1.33em 0}.task-editor[data-v-5adf557a] h5{margin:1.67em 0}.task-editor[data-v-5adf557a] h6{margin:2.33em 0}.task-editor .task-editor-operate[data-v-5adf557a]{position:absolute;top:0;left:0;width:1px;opacity:0;visibility:hidden;pointer-events:none}.task-tag-select[data-v-e09d999e]{width:100%;display:flex;flex-direction:column}.task-tag-select.no-search .search-box[data-v-e09d999e]{display:none}.task-tag-select.no-search .tag-list .tag-item[data-v-e09d999e]:first-child{margin-top:0}.task-tag-select .search-box[data-v-e09d999e]{padding-bottom:8px;border-bottom:1px solid #eee}.task-tag-select .search-box .search-input[data-v-e09d999e]{width:100%;height:34px;padding:0 12px;border:1px solid #dcdfe6;border-radius:4px;outline:none}.task-tag-select .search-box .search-input[data-v-e09d999e]:focus{border-color:#84c56a}.task-tag-select .tag-list[data-v-e09d999e]{flex:1;overflow-y:auto;max-height:300px;margin:0 -12px;padding:0 12px}.task-tag-select .tag-list .tag-item[data-v-e09d999e]{display:flex;align-items:flex-start;padding:8px 12px;cursor:pointer;border-radius:6px;margin-bottom:6px}.task-tag-select .tag-list .tag-item[data-v-e09d999e]:first-child{margin-top:12px}.task-tag-select .tag-list .tag-item[data-v-e09d999e]:last-child{margin-bottom:12px}.task-tag-select .tag-list .tag-item[data-v-e09d999e]:hover{background-color:#f5f7fa}.task-tag-select .tag-list .tag-item.is-selected[data-v-e09d999e]{background-color:#ecf5ff}.task-tag-select .tag-list .tag-item .tag-color[data-v-e09d999e]{width:16px;height:16px;border-radius:4px;margin-right:8px;margin-top:2px}.task-tag-select .tag-list .tag-item .tag-info[data-v-e09d999e]{flex:1}.task-tag-select .tag-list .tag-item .tag-info .tag-name[data-v-e09d999e]{line-height:20px;font-size:14px;color:#303133}.task-tag-select .tag-list .tag-item .tag-info .tag-desc[data-v-e09d999e]{font-size:12px;color:#909399;margin-top:2px}.task-tag-select .tag-list .tag-item .tag-check[data-v-e09d999e]{color:#84c56a;margin-left:12px;height:20px;display:flex;align-items:center}.task-tag-select .tag-list .no-data[data-v-e09d999e]{text-align:center;color:#909399;padding:24px 0;margin-bottom:12px}.task-tag-select .footer-box[data-v-e09d999e]{border-top:1px solid #eee;padding-top:8px}.task-tag-select .footer-box .add-button[data-v-e09d999e]{display:flex;align-items:center;justify-content:center;padding:4px 0 2px;cursor:pointer;color:#84c56a;border-radius:6px;transition:color .2s}.task-tag-select .footer-box .add-button[data-v-e09d999e]:hover{color:#a2d98d}.task-tag-select .footer-box .add-button i[data-v-e09d999e]{margin-right:4px}.task-content-history .ivu-page[data-v-204b70c0]{margin-top:12px;display:flex;align-items:center;justify-content:center}
|
||||
.task-editor[data-v-4e70a0a5]{position:relative;word-break:break-all}.task-editor[data-v-4e70a0a5] .mce-content-body,.task-editor[data-v-4e70a0a5] .task-editor-content{line-height:1.6}.task-editor[data-v-4e70a0a5] p{margin:.3em 0}.task-editor[data-v-4e70a0a5] blockquote,.task-editor[data-v-4e70a0a5] pre,.task-editor[data-v-4e70a0a5] ul,.task-editor[data-v-4e70a0a5] ol{margin:1em 0}.task-editor[data-v-4e70a0a5] ul,.task-editor[data-v-4e70a0a5] ol{margin-left:1.5em;padding-left:1.5em}.task-editor[data-v-4e70a0a5] li{margin:.25em 0}.task-editor[data-v-4e70a0a5] h1{margin:.67em 0}.task-editor[data-v-4e70a0a5] h2{margin:.83em 0}.task-editor[data-v-4e70a0a5] h3{margin:1em 0}.task-editor[data-v-4e70a0a5] h4{margin:1.33em 0}.task-editor[data-v-4e70a0a5] h5{margin:1.67em 0}.task-editor[data-v-4e70a0a5] h6{margin:2.33em 0}.task-editor .task-editor-operate[data-v-4e70a0a5]{position:absolute;top:0;left:0;width:1px;opacity:0;visibility:hidden;pointer-events:none}.task-tag-select[data-v-e09d999e]{width:100%;display:flex;flex-direction:column}.task-tag-select.no-search .search-box[data-v-e09d999e]{display:none}.task-tag-select.no-search .tag-list .tag-item[data-v-e09d999e]:first-child{margin-top:0}.task-tag-select .search-box[data-v-e09d999e]{padding-bottom:8px;border-bottom:1px solid #eee}.task-tag-select .search-box .search-input[data-v-e09d999e]{width:100%;height:34px;padding:0 12px;border:1px solid #dcdfe6;border-radius:4px;outline:none}.task-tag-select .search-box .search-input[data-v-e09d999e]:focus{border-color:#84c56a}.task-tag-select .tag-list[data-v-e09d999e]{flex:1;overflow-y:auto;max-height:300px;margin:0 -12px;padding:0 12px}.task-tag-select .tag-list .tag-item[data-v-e09d999e]{display:flex;align-items:flex-start;padding:8px 12px;cursor:pointer;border-radius:6px;margin-bottom:6px}.task-tag-select .tag-list .tag-item[data-v-e09d999e]:first-child{margin-top:12px}.task-tag-select .tag-list .tag-item[data-v-e09d999e]:last-child{margin-bottom:12px}.task-tag-select .tag-list .tag-item[data-v-e09d999e]:hover{background-color:#f5f7fa}.task-tag-select .tag-list .tag-item.is-selected[data-v-e09d999e]{background-color:#ecf5ff}.task-tag-select .tag-list .tag-item .tag-color[data-v-e09d999e]{width:16px;height:16px;border-radius:4px;margin-right:8px;margin-top:2px}.task-tag-select .tag-list .tag-item .tag-info[data-v-e09d999e]{flex:1}.task-tag-select .tag-list .tag-item .tag-info .tag-name[data-v-e09d999e]{line-height:20px;font-size:14px;color:#303133}.task-tag-select .tag-list .tag-item .tag-info .tag-desc[data-v-e09d999e]{font-size:12px;color:#909399;margin-top:2px}.task-tag-select .tag-list .tag-item .tag-check[data-v-e09d999e]{color:#84c56a;margin-left:12px;height:20px;display:flex;align-items:center}.task-tag-select .tag-list .no-data[data-v-e09d999e]{text-align:center;color:#909399;padding:24px 0;margin-bottom:12px}.task-tag-select .footer-box[data-v-e09d999e]{border-top:1px solid #eee;padding-top:8px}.task-tag-select .footer-box .add-button[data-v-e09d999e]{display:flex;align-items:center;justify-content:center;padding:4px 0 2px;cursor:pointer;color:#84c56a;border-radius:6px;transition:color .2s}.task-tag-select .footer-box .add-button[data-v-e09d999e]:hover{color:#a2d98d}.task-tag-select .footer-box .add-button i[data-v-e09d999e]{margin-right:4px}.task-content-history .ivu-page[data-v-a0030d34]{margin-top:12px;display:flex;align-items:center;justify-content:center}
|
||||
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.99bc3a02.css
vendored
7
public/js/build/app.99bc3a02.css
vendored
File diff suppressed because one or more lines are too long
7
public/js/build/app.d48b83e8.css
vendored
Normal file
7
public/js/build/app.d48b83e8.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
@ -1 +1 @@
|
||||
import{m}from"./vuex.cc7cb26e.js";import{M as e}from"./index.96ab2c5d.js";import{n as a}from"./app.20ce4f8e.js";import"./vue.adba9046.js";import"./@babel.9410f858.js";import"./view-design-hi.f1128b4d.js";import"./@micro-zoe.39406924.js";import"./DialogWrapper.1f50fe2a.js";import"./index.571c9d21.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.a3251af4.js";import"./webhook.378987f3.js";import"./jquery.26755d2b.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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"./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"./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 lr=function(){return c.exports}();export{lr as default};
|
||||
import{m}from"./vuex.cc7cb26e.js";import{M as e}from"./index.0c10ac32.js";import{n as a}from"./app.155cfddd.js";import"./vue.adba9046.js";import"./@babel.9410f858.js";import"./view-design-hi.f1128b4d.js";import"./@micro-zoe.39406924.js";import"./DialogWrapper.47fb52c9.js";import"./index.da0cb982.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.e8d54143.js";import"./webhook.378987f3.js";import"./jquery.b8b91492.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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"./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"./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 lr=function(){return c.exports}();export{lr as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{n as l}from"./app.20ce4f8e.js";import"./jquery.26755d2b.js";import"./@babel.9410f858.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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.f1128b4d.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.155cfddd.js";import"./jquery.b8b91492.js";import"./@babel.9410f858.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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.f1128b4d.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 +1 @@
|
||||
import{D as p}from"./DialogWrapper.1f50fe2a.js";import{m}from"./vuex.cc7cb26e.js";import{n as a}from"./app.20ce4f8e.js";import"./index.571c9d21.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./@babel.9410f858.js";import"./vue.adba9046.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.a3251af4.js";import"./webhook.378987f3.js";import"./jquery.26755d2b.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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"./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.f1128b4d.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 st=function(){return l.exports}();export{st as default};
|
||||
import{D as p}from"./DialogWrapper.47fb52c9.js";import{m}from"./vuex.cc7cb26e.js";import{n as a}from"./app.155cfddd.js";import"./index.da0cb982.js";import"./vue-virtual-scroll-list-hi.74ad83f0.js";import"./@babel.9410f858.js";import"./vue.adba9046.js";import"./lodash.8fcd6fd4.js";import"./ImgUpload.e8d54143.js";import"./webhook.378987f3.js";import"./jquery.b8b91492.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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"./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.f1128b4d.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 st=function(){return l.exports}();export{st as default};
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
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 from"./FileContent.529916bc.js";import m from"./FilePreview.293a42e1.js";import{n as l}from"./app.20ce4f8e.js";import"./openpgp_hi.15f91b1d.js";import"./IFrame.b849e339.js";import"./jquery.26755d2b.js";import"./@babel.9410f858.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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.f1128b4d.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.7dd80233.js";import m from"./FilePreview.3a085228.js";import{n as l}from"./app.155cfddd.js";import"./openpgp_hi.15f91b1d.js";import"./IFrame.a92ca567.js";import"./jquery.b8b91492.js";import"./@babel.9410f858.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var s=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"single-file"},[i("PageTitle",{attrs:{title:t.pageName}}),t.loadIng>0?i("Loading"):t.fileInfo?[t.isPreview?i("FilePreview",{attrs:{code:t.code,file:t.fileInfo,historyId:t.historyId,headerShow:!t.$isEEUIApp}}):i("FileContent",{attrs:{file:t.fileInfo},model:{value:t.fileShow,callback:function(r){t.fileShow=r},expression:"fileShow"}})]:t._e()],2)},p=[];const a={components:{FilePreview:m,FileContent:n},data(){return{loadIng:0,code:null,fileShow:!0,fileInfo:null}},mounted(){},computed:{historyId(){return this.$route.query?$A.runNum(this.$route.query.history_id):0},isPreview(){return this.windowPortrait||this.code||this.historyId>0||this.fileInfo&&this.fileInfo.permission===0},pageName(){return this.$route.query&&this.$route.query.history_title?this.$route.query.history_title:this.fileInfo?`${this.fileInfo.name} [${this.fileInfo.created_at}]`:""}},watch:{$route:{handler(){this.getInfo()},immediate:!0}},methods:{getInfo(){let{codeOrFileId:t}=this.$route.params,e={id:t};if(/^\d+$/.test(t))this.code=null;else if(t)this.code=t;else return;setTimeout(i=>{this.loadIng++},600),this.$store.dispatch("call",{url:"file/one",data:e}).then(({data:i})=>{this.fileInfo=i}).catch(({msg:i})=>{$A.modalError({content:i,onOk:()=>{window.close()}})}).finally(i=>{this.loadIng--})}}},o={};var f=l(a,s,p,!1,u,"662d0b64",null,null);function u(t){for(let e in o)this[e]=o[e]}var st=function(){return f.exports}();export{st as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{n as m}from"./app.20ce4f8e.js";import"./jquery.26755d2b.js";import"./@babel.9410f858.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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.f1128b4d.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.155cfddd.js";import"./jquery.b8b91492.js";import"./@babel.9410f858.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var e=function(){var t=this,o=t.$createElement,i=t._self._c||o;return i("div")},n=[];const p={data(){return{}},mounted(){if(/^https?:/i.test(window.location.protocol)){let t=null;if(this.$router.mode==="hash"?$A.stringLength(window.location.pathname)>2&&(t=`${window.location.origin}/#${window.location.pathname}${window.location.search}`):this.$router.mode==="history"&&$A.strExists(window.location.href,"/#/")&&(t=window.location.href.replace("/#/","/")),t)throw this.$store.dispatch("userUrl",t).then(o=>{window.location.href=o}),SyntaxError()}},activated(){this.start()},methods:{start(){this.userId>0?this.goForward({name:"manage-dashboard"},!0):this.goForward({name:"login"},!0)}}},r={};var a=m(p,e,n,!1,s,null,null,null);function s(t){for(let o in r)this[o]=r[o]}var rt=function(){return a.exports}();export{rt as default};
|
||||
@ -1 +1 @@
|
||||
import{_ as m}from"./openpgp_hi.15f91b1d.js";import{e as n}from"./index.40a8e116.js";import{n as p}from"./app.20ce4f8e.js";import"./jquery.26755d2b.js";import"./@babel.9410f858.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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.f1128b4d.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.0e956fa2.js"),["js/build/editor.0e956fa2.js","js/build/editor.90492550.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.20ce4f8e.js","js/build/app.99bc3a02.css","js/build/jquery.26755d2b.js","js/build/dayjs.29a2c04b.js","js/build/localforage.a7f8d307.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.f1128b4d.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.a3251af4.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.155cfddd.js";import"./jquery.b8b91492.js";import"./@babel.9410f858.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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.f1128b4d.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.69ce8e47.js"),["js/build/editor.69ce8e47.js","js/build/editor.90492550.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.155cfddd.js","js/build/app.d48b83e8.css","js/build/jquery.b8b91492.js","js/build/dayjs.fed09e57.js","js/build/localforage.fcd54ffc.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.f1128b4d.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.e8d54143.js"])},data(){return{ready:!1,content:""}},async mounted(){await $A.loadScriptS(["js/katex/katex.min.js","js/katex/katex.min.css","js/mermaid.min.js"]),this.ready=!0},watch:{value:{handler(t){t==null&&(t=""),this.content=t},immediate:!0},content(t){this.$emit("input",t)}}},r={};var c=p(l,a,s,!1,_,null,null,null);function _(t){for(let o in r)this[o]=r[o]}var nt=function(){return c.exports}();export{nt as default};
|
||||
File diff suppressed because one or more lines are too long
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{h as e,l as n,r as s,n as p}from"./app.20ce4f8e.js";import{m as l}from"./vuex.cc7cb26e.js";import"./jquery.26755d2b.js";import"./@babel.9410f858.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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.f1128b4d.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",{staticStyle:{"margin-left":"8px"},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{h as e,l as n,r as s,n as p}from"./app.155cfddd.js";import{m as l}from"./vuex.cc7cb26e.js";import"./jquery.b8b91492.js";import"./@babel.9410f858.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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.f1128b4d.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",{staticStyle:{"margin-left":"8px"},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.15827612.js
vendored
16
public/js/build/manage.15827612.js
vendored
File diff suppressed because one or more lines are too long
16
public/js/build/manage.766b6dc5.js
vendored
Normal file
16
public/js/build/manage.766b6dc5.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
@charset "UTF-8";.mcp-helper-content .mcp-section[data-v-00627e4a]{margin-top:20px}.mcp-helper-content .mcp-section h3[data-v-00627e4a]{font-weight:600;margin-bottom:12px;color:#333}.mcp-helper-content .mcp-section p[data-v-00627e4a]{margin-bottom:10px;color:#666;line-height:1.6}.mcp-helper-content .mcp-section .mcp-config-tabs[data-v-00627e4a]{margin-top:12px}.mcp-helper-content .mcp-section .mcp-config-tabs .mcp-config-hint[data-v-00627e4a]{margin:8px 0;color:#666}.mcp-helper-content .mcp-section .mcp-config-tabs .mcp-config-hint code[data-v-00627e4a]{background:#f0f0f0;padding:2px 6px;border-radius:3px}.mcp-helper-content .mcp-section .mcp-config-tabs .mcp-config-hint .mcp-path[data-v-00627e4a]{display:block;margin-top:4px;color:#999}.mcp-helper-content .mcp-section .mcp-config-tabs .mcp-config-hint.mcp-config-note[data-v-00627e4a]{margin-top:16px;padding-top:12px;border-top:1px dashed #e4e7ed}.mcp-helper-content .mcp-section .mcp-code-block[data-v-00627e4a]{position:relative;background:#f5f7fa;border:1px solid #e4e7ed;border-radius:4px;padding:12px 70px 12px 12px;margin:8px 0}.mcp-helper-content .mcp-section .mcp-code-block code[data-v-00627e4a],.mcp-helper-content .mcp-section .mcp-code-block pre[data-v-00627e4a]{font-family:Consolas,Monaco,Courier New,monospace;font-size:13px}.mcp-helper-content .mcp-section .mcp-code-block pre[data-v-00627e4a]{margin:0;line-height:1.5;color:#333;overflow-x:auto;white-space:pre-wrap;word-break:break-all}.mcp-helper-content .mcp-section .mcp-code-block .mcp-copy-btn[data-v-00627e4a]{position:absolute;top:8px;right:8px}.mcp-helper-content .mcp-section .mcp-category[data-v-00627e4a]{margin-top:16px}.mcp-helper-content .mcp-section .mcp-category[data-v-00627e4a]:first-child{margin-top:12px}.mcp-helper-content .mcp-section .mcp-category h4[data-v-00627e4a]{font-weight:600;color:#515a6e;margin-bottom:8px;padding-left:8px;border-left:3px solid #2d8cf0}.mcp-helper-content .mcp-section .mcp-examples[data-v-00627e4a]{margin:0;padding-left:20px}.mcp-helper-content .mcp-section .mcp-examples li[data-v-00627e4a]{margin:6px 0;color:#666;line-height:1.6}.mcp-helper-content .mcp-section .mcp-examples li[data-v-00627e4a]:before{content:"\2022";color:#2d8cf0;font-weight:700;display:inline-block;width:1em;margin-left:-1em}.mcp-helper-content code[data-v-00627e4a]{background:#f5f7fa;padding:2px 6px;border-radius:3px;font-family:Consolas,Monaco,Courier New,monospace;color:#e96900}iframe[data-v-6e0f538a]{width:100%;height:100%;padding:0;margin:0;border:0;float:left}.delcon[data-v-6e0f538a]{position:absolute;right:0;padding:5px!important}.delcon[data-v-6e0f538a]:hover{color:#ed4014!important}.page-approve .approve-details{border-radius:8px}.page-approve .ivu-tabs-nav{display:flex;width:350px}@media (width <= 1010px){.page-approve .ivu-tabs-nav{width:100%}}.page-approve .ivu-tabs-nav .ivu-tabs-tab{font-size:15px;flex:1;text-align:center}.page-approve-initiate .ivu-modal-body{padding:16px 22px 2px!important}
|
||||
@charset "UTF-8";.mcp-helper-content .mcp-section[data-v-00627e4a]{margin-top:20px}.mcp-helper-content .mcp-section h3[data-v-00627e4a]{font-weight:600;margin-bottom:12px;color:#333}.mcp-helper-content .mcp-section p[data-v-00627e4a]{margin-bottom:10px;color:#666;line-height:1.6}.mcp-helper-content .mcp-section .mcp-config-tabs[data-v-00627e4a]{margin-top:12px}.mcp-helper-content .mcp-section .mcp-config-tabs .mcp-config-hint[data-v-00627e4a]{margin:8px 0;color:#666}.mcp-helper-content .mcp-section .mcp-config-tabs .mcp-config-hint code[data-v-00627e4a]{background:#f0f0f0;padding:2px 6px;border-radius:3px}.mcp-helper-content .mcp-section .mcp-config-tabs .mcp-config-hint .mcp-path[data-v-00627e4a]{display:block;margin-top:4px;color:#999}.mcp-helper-content .mcp-section .mcp-config-tabs .mcp-config-hint.mcp-config-note[data-v-00627e4a]{margin-top:16px;padding-top:12px;border-top:1px dashed #e4e7ed}.mcp-helper-content .mcp-section .mcp-code-block[data-v-00627e4a]{position:relative;background:#f5f7fa;border:1px solid #e4e7ed;border-radius:4px;padding:12px 70px 12px 12px;margin:8px 0}.mcp-helper-content .mcp-section .mcp-code-block code[data-v-00627e4a],.mcp-helper-content .mcp-section .mcp-code-block pre[data-v-00627e4a]{font-family:Consolas,Monaco,Courier New,monospace;font-size:13px}.mcp-helper-content .mcp-section .mcp-code-block pre[data-v-00627e4a]{margin:0;line-height:1.5;color:#333;overflow-x:auto;white-space:pre-wrap;word-break:break-all}.mcp-helper-content .mcp-section .mcp-code-block .mcp-copy-btn[data-v-00627e4a]{position:absolute;top:8px;right:8px}.mcp-helper-content .mcp-section .mcp-category[data-v-00627e4a]{margin-top:16px}.mcp-helper-content .mcp-section .mcp-category[data-v-00627e4a]:first-child{margin-top:12px}.mcp-helper-content .mcp-section .mcp-category h4[data-v-00627e4a]{font-weight:600;color:#515a6e;margin-bottom:8px;padding-left:8px;border-left:3px solid #2d8cf0}.mcp-helper-content .mcp-section .mcp-examples[data-v-00627e4a]{margin:0;padding-left:20px}.mcp-helper-content .mcp-section .mcp-examples li[data-v-00627e4a]{margin:6px 0;color:#666;line-height:1.6}.mcp-helper-content .mcp-section .mcp-examples li[data-v-00627e4a]:before{content:"\2022";color:#2d8cf0;font-weight:700;display:inline-block;width:1em;margin-left:-1em}.mcp-helper-content code[data-v-00627e4a]{background:#f5f7fa;padding:2px 6px;border-radius:3px;font-family:Consolas,Monaco,Courier New,monospace;color:#e96900}.task-template-browser .search-wrap[data-v-ebaec12e]{margin-bottom:8px}.task-template-browser .list-wrap[data-v-ebaec12e]{max-height:420px;overflow-y:auto}.task-template-browser .list-wrap>div[data-v-ebaec12e]:last-child{margin-bottom:12px}.task-template-browser .item[data-v-ebaec12e]{padding:10px 12px;border-radius:4px;cursor:pointer}.task-template-browser .item.selected[data-v-ebaec12e]{background:rgba(64,158,255,.1)}.task-template-browser .item .item-name[data-v-ebaec12e]{font-weight:500}.task-template-browser .item .item-meta[data-v-ebaec12e]{margin-top:4px;font-size:12px;color:#909399}.task-template-browser .item .item-meta .creator[data-v-ebaec12e]{margin-left:4px}.task-template-browser .loading[data-v-ebaec12e]{display:flex;align-items:center;justify-content:center;gap:8px}.task-template-browser .loading .common-loading[data-v-ebaec12e]{width:18px;height:18px;margin:0}.task-template-browser .loading[data-v-ebaec12e],.task-template-browser .empty[data-v-ebaec12e]{text-align:center;padding:20px;color:#909399}iframe[data-v-6e0f538a]{width:100%;height:100%;padding:0;margin:0;border:0;float:left}.delcon[data-v-6e0f538a]{position:absolute;right:0;padding:5px!important}.delcon[data-v-6e0f538a]:hover{color:#ed4014!important}.page-approve .approve-details{border-radius:8px}.page-approve .ivu-tabs-nav{display:flex;width:350px}@media (width <= 1010px){.page-approve .ivu-tabs-nav{width:100%}}.page-approve .ivu-tabs-nav .ivu-tabs-tab{font-size:15px;flex:1;text-align:center}.page-approve-initiate .ivu-modal-body{padding:16px 22px 2px!important}
|
||||
@ -1 +1 @@
|
||||
import{n as a}from"./app.20ce4f8e.js";import"./jquery.26755d2b.js";import"./@babel.9410f858.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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.f1128b4d.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.155cfddd.js";import"./jquery.b8b91492.js";import"./@babel.9410f858.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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.f1128b4d.js";import"./html-to-md.f297036e.js";import"./lodash.8fcd6fd4.js";import"./vue-router.2d566cd7.js";import"./vue-clipboard2.fd43a5bc.js";import"./clipboard.37b37361.js";import"./vuedraggable.f464b992.js";import"./sortablejs.3488b922.js";import"./vue-resize-observer.5af23a43.js";import"./element-sea.f8a64907.js";import"./deepmerge.cecf392e.js";import"./resize-observer-polyfill.5d591c5f.js";import"./throttle-debounce.7c3948b2.js";import"./babel-helper-vue-jsx-merge-props.5ed215c3.js";import"./normalize-wheel.2a034b9f.js";import"./async-validator.dca2b951.js";import"./babel-runtime.4773988a.js";import"./core-js.314b4a1d.js";var s=function(){var i=this,t=i.$createElement,r=i._self._c||t;return r("div")},u=[];const c={mounted(){const{meetingId:i,sharekey:t}=this.$route.params,{nickname:r,avatar:m,audio:p,video:n,type:o}=this.$route.query;this.$store.dispatch("showMeetingWindow",{type:["direct","join"].includes(o)?o:"join",meetingid:i,meetingSharekey:t,meetingNickname:r,meetingAvatar:m,meetingAudio:p,meetingVideo:n,meetingdisabled:!0})},render(){return null}},e={};var d=a(c,s,u,!1,l,null,null,null);function l(i){for(let t in e)this[t]=e[t]}var pt=function(){return d.exports}();export{pt as default};
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{m as i}from"./vuex.cc7cb26e.js";import{n as m}from"./app.20ce4f8e.js";import"./jquery.26755d2b.js";import"./@babel.9410f858.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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.f1128b4d.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,s=t.$createElement,r=t._self._c||s;return r("div",{staticClass:"setting-item submit"},[r("Form",t._b({ref:"formDatum",attrs:{model:t.formDatum,rules:t.ruleDatum},nativeOn:{submit:function(e){e.preventDefault()}}},"Form",t.formOptions,!1),[t.userInfo.changepass?r("Alert",{staticStyle:{"margin-bottom":"32px"},attrs:{type:"warning",showIcon:""}},[t._v(t._s(t.$L("\u8BF7\u5148\u4FEE\u6539\u767B\u5F55\u5BC6\u7801\uFF01")))]):t._e(),r("FormItem",{attrs:{label:t.$L("\u65E7\u5BC6\u7801"),prop:"oldpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.oldpass,callback:function(e){t.$set(t.formDatum,"oldpass",e)},expression:"formDatum.oldpass"}})],1),r("FormItem",{attrs:{label:t.$L("\u65B0\u5BC6\u7801"),prop:"newpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.newpass,callback:function(e){t.$set(t.formDatum,"newpass",e)},expression:"formDatum.newpass"}})],1),r("FormItem",{attrs:{label:t.$L("\u786E\u8BA4\u65B0\u5BC6\u7801"),prop:"checkpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.checkpass,callback:function(e){t.$set(t.formDatum,"checkpass",e)},expression:"formDatum.checkpass"}})],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",{staticStyle:{"margin-left":"8px"},attrs:{loading:t.loadIng>0},on:{click:t.resetForm}},[t._v(t._s(t.$L("\u91CD\u7F6E")))])],1)],1)},p=[];const n={data(){return{loadIng:0,formDatum:{oldpass:"",newpass:"",checkpass:""},ruleDatum:{oldpass:[{required:!0,message:this.$L("\u8BF7\u8F93\u5165\u65E7\u5BC6\u7801\uFF01"),trigger:"change"},{type:"string",min:6,message:this.$L("\u5BC6\u7801\u957F\u5EA6\u81F3\u5C116\u4F4D\uFF01"),trigger:"change"}],newpass:[{validator:(t,s,r)=>{s===""?r(new Error(this.$L("\u8BF7\u8F93\u5165\u65B0\u5BC6\u7801\uFF01"))):(this.formDatum.checkpass!==""&&this.$refs.formDatum.validateField("checkpass"),r())},required:!0,trigger:"change"},{type:"string",min:6,message:this.$L("\u5BC6\u7801\u957F\u5EA6\u81F3\u5C116\u4F4D\uFF01"),trigger:"change"}],checkpass:[{validator:(t,s,r)=>{s===""?r(new Error(this.$L("\u8BF7\u91CD\u65B0\u8F93\u5165\u65B0\u5BC6\u7801\uFF01"))):s!==this.formDatum.newpass?r(new Error(this.$L("\u4E24\u6B21\u5BC6\u7801\u8F93\u5165\u4E0D\u4E00\u81F4\uFF01"))):r()},required:!0,trigger:"change"}]}}},computed:{...i(["userInfo","formOptions"])},methods:{submitForm(){this.$refs.formDatum.validate(t=>{t&&(this.loadIng++,this.$store.dispatch("call",{url:"users/editpass",data:this.formDatum}).then(({data:s})=>{$A.messageSuccess("\u4FEE\u6539\u6210\u529F"),this.$store.dispatch("saveUserInfo",s),this.$refs.formDatum.resetFields()}).catch(({msg:s})=>{$A.modalError(s)}).finally(s=>{this.loadIng--}))})},resetForm(){this.$refs.formDatum.resetFields()}}},o={};var l=m(n,a,p,!1,u,null,null,null);function u(t){for(let s in o)this[s]=o[s]}var ot=function(){return l.exports}();export{ot as default};
|
||||
import{m as i}from"./vuex.cc7cb26e.js";import{n as m}from"./app.155cfddd.js";import"./jquery.b8b91492.js";import"./@babel.9410f858.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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.f1128b4d.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,s=t.$createElement,r=t._self._c||s;return r("div",{staticClass:"setting-item submit"},[r("Form",t._b({ref:"formDatum",attrs:{model:t.formDatum,rules:t.ruleDatum},nativeOn:{submit:function(e){e.preventDefault()}}},"Form",t.formOptions,!1),[t.userInfo.changepass?r("Alert",{staticStyle:{"margin-bottom":"32px"},attrs:{type:"warning",showIcon:""}},[t._v(t._s(t.$L("\u8BF7\u5148\u4FEE\u6539\u767B\u5F55\u5BC6\u7801\uFF01")))]):t._e(),r("FormItem",{attrs:{label:t.$L("\u65E7\u5BC6\u7801"),prop:"oldpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.oldpass,callback:function(e){t.$set(t.formDatum,"oldpass",e)},expression:"formDatum.oldpass"}})],1),r("FormItem",{attrs:{label:t.$L("\u65B0\u5BC6\u7801"),prop:"newpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.newpass,callback:function(e){t.$set(t.formDatum,"newpass",e)},expression:"formDatum.newpass"}})],1),r("FormItem",{attrs:{label:t.$L("\u786E\u8BA4\u65B0\u5BC6\u7801"),prop:"checkpass"}},[r("Input",{attrs:{type:"password"},model:{value:t.formDatum.checkpass,callback:function(e){t.$set(t.formDatum,"checkpass",e)},expression:"formDatum.checkpass"}})],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",{staticStyle:{"margin-left":"8px"},attrs:{loading:t.loadIng>0},on:{click:t.resetForm}},[t._v(t._s(t.$L("\u91CD\u7F6E")))])],1)],1)},p=[];const n={data(){return{loadIng:0,formDatum:{oldpass:"",newpass:"",checkpass:""},ruleDatum:{oldpass:[{required:!0,message:this.$L("\u8BF7\u8F93\u5165\u65E7\u5BC6\u7801\uFF01"),trigger:"change"},{type:"string",min:6,message:this.$L("\u5BC6\u7801\u957F\u5EA6\u81F3\u5C116\u4F4D\uFF01"),trigger:"change"}],newpass:[{validator:(t,s,r)=>{s===""?r(new Error(this.$L("\u8BF7\u8F93\u5165\u65B0\u5BC6\u7801\uFF01"))):(this.formDatum.checkpass!==""&&this.$refs.formDatum.validateField("checkpass"),r())},required:!0,trigger:"change"},{type:"string",min:6,message:this.$L("\u5BC6\u7801\u957F\u5EA6\u81F3\u5C116\u4F4D\uFF01"),trigger:"change"}],checkpass:[{validator:(t,s,r)=>{s===""?r(new Error(this.$L("\u8BF7\u91CD\u65B0\u8F93\u5165\u65B0\u5BC6\u7801\uFF01"))):s!==this.formDatum.newpass?r(new Error(this.$L("\u4E24\u6B21\u5BC6\u7801\u8F93\u5165\u4E0D\u4E00\u81F4\uFF01"))):r()},required:!0,trigger:"change"}]}}},computed:{...i(["userInfo","formOptions"])},methods:{submitForm(){this.$refs.formDatum.validate(t=>{t&&(this.loadIng++,this.$store.dispatch("call",{url:"users/editpass",data:this.formDatum}).then(({data:s})=>{$A.messageSuccess("\u4FEE\u6539\u6210\u529F"),this.$store.dispatch("saveUserInfo",s),this.$refs.formDatum.resetFields()}).catch(({msg:s})=>{$A.modalError(s)}).finally(s=>{this.loadIng--}))})},resetForm(){this.$refs.formDatum.resetFields()}}},o={};var l=m(n,a,p,!1,u,null,null,null);function u(t){for(let s in o)this[s]=o[s]}var ot=function(){return l.exports}();export{ot as default};
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{n as m}from"./app.20ce4f8e.js";import"./jquery.26755d2b.js";import"./@babel.9410f858.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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.f1128b4d.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 r=this,t=r.$createElement,i=r._self._c||t;return i("div")},e=[];const n={},o={};var _=m(n,p,e,!1,s,null,null,null);function s(r){for(let t in o)this[t]=o[t]}var ot=function(){return _.exports}();export{ot as default};
|
||||
import{n as m}from"./app.155cfddd.js";import"./jquery.b8b91492.js";import"./@babel.9410f858.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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.f1128b4d.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 r=this,t=r.$createElement,i=r._self._c||t;return i("div")},e=[];const n={},o={};var _=m(n,p,e,!1,s,null,null,null);function s(r){for(let t in o)this[t]=o[t]}var ot=function(){return _.exports}();export{ot as default};
|
||||
@ -1 +1 @@
|
||||
import{V as e,d as p,a as s,b as n,c as a,_ as l,e as u,v as _}from"./@kangc.b5fe0a56.js";import{P as c}from"./prismjs.94ec9288.js";import{l as v,u as o,n as d}from"./app.20ce4f8e.js";import{p as f}from"./index.40a8e116.js";import"./@babel.9410f858.js";import"./vue.adba9046.js";import"./copy-to-clipboard.a53c061d.js";import"./toggle-selection.d2487283.js";import"./jquery.26755d2b.js";import"./dayjs.29a2c04b.js";import"./localforage.a7f8d307.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"./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.f1128b4d.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 h=function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"vmpreview-wrapper",on:{click:t.handleClick}},[i("v-md-preview",{attrs:{text:t.previewContent}})],1)},g=[];/^zh/.test(v)?e.lang.use("zh-CN",p):e.lang.use("en-US",s);e.use(n());e.use(a());e.use(l());e.use(u());const w={mixins:[f],components:{[e.name]:e},created(){e.use(_,{Prism:c,extend(t){o.initReasoningPlugin(t)}})},computed:{previewContent({value:t}){return o.clearEmptyReasoning(t)}},methods:{handleClick({target:t}){if(t.nodeName==="IMG"){const r=[...this.$el.querySelectorAll("img").values()].map(i=>i.src);if(r.length===0)return;this.$store.dispatch("previewImage",{index:t.src,list:r})}}}},m={};var x=d(w,h,g,!1,C,"6797ab07",null,null);function C(t){for(let r in m)this[r]=m[r]}var wt=function(){return x.exports}();export{wt as default};
|
||||
import{V as e,d as p,a as s,b as n,c as a,_ as l,e as u,v as _}from"./@kangc.b5fe0a56.js";import{P as c}from"./prismjs.94ec9288.js";import{l as v,u as o,n as d}from"./app.155cfddd.js";import{p as f}from"./index.40a8e116.js";import"./@babel.9410f858.js";import"./vue.adba9046.js";import"./copy-to-clipboard.a53c061d.js";import"./toggle-selection.d2487283.js";import"./jquery.b8b91492.js";import"./dayjs.fed09e57.js";import"./localforage.fcd54ffc.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"./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.f1128b4d.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 h=function(){var t=this,r=t.$createElement,i=t._self._c||r;return i("div",{staticClass:"vmpreview-wrapper",on:{click:t.handleClick}},[i("v-md-preview",{attrs:{text:t.previewContent}})],1)},g=[];/^zh/.test(v)?e.lang.use("zh-CN",p):e.lang.use("en-US",s);e.use(n());e.use(a());e.use(l());e.use(u());const w={mixins:[f],components:{[e.name]:e},created(){e.use(_,{Prism:c,extend(t){o.initReasoningPlugin(t)}})},computed:{previewContent({value:t}){return o.clearEmptyReasoning(t)}},methods:{handleClick({target:t}){if(t.nodeName==="IMG"){const r=[...this.$el.querySelectorAll("img").values()].map(i=>i.src);if(r.length===0)return;this.$store.dispatch("previewImage",{index:t.src,list:r})}}}},m={};var x=d(w,h,g,!1,C,"6797ab07",null,null);function C(t){for(let r in m)this[r]=m[r]}var wt=function(){return x.exports}();export{wt as default};
|
||||
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