23 Commits

Author SHA1 Message Date
谦君
1ad051cd6d Update ResourceIcon.vue 2025-12-27 04:02:17 +08:00
谦君
fda15646eb Update package.json 2025-12-27 03:58:54 +08:00
谦君
6a9846c6df Update package.json 2025-12-27 03:58:15 +08:00
谦君
49753566c3 优化webdav相关 2025-12-27 01:37:35 +08:00
谦君
66783f896c 补全翻译 2025-12-27 01:04:14 +08:00
谦君
7cc885c62a Delete splash.xml 2025-12-27 00:18:40 +08:00
谦君
5c6404d86a 优化移动端开屏 2025-12-27 00:12:49 +08:00
谦君
010ea137ac perf: 优化安卓WebView性能与调试配置
MainActivity中为WebView启用硬件加速、DOM存储、数据库及默认缓存模式,提升性能与兼容性。capacitor.config.ts开启webContentsDebugging,便于调试排查问题。
2025-12-26 23:44:22 +08:00
谦君
6dbca76252 Update index.html 2025-12-26 23:37:23 +08:00
谦君
c047ffb88e Create favicon.ico 2025-12-26 22:25:12 +08:00
谦君
6f8adfa586 build: 替换autoprefixer为lightningcss并优化依赖
移除autoprefixer,改用lightningcss处理CSS,提升构建兼容性与性能。package.json、pnpm-lock.yaml、vite.config.ts同步调整依赖与配置,支持Android 5+/iOS 10+/Chrome 60+等目标环境。补充PWA苹果图标,删除favicon.ico。多语言任务目标文本细化和丰富,提升本地化体验。安卓端gradle配置补充capacitor-app与capacitor-filesystem依赖。
2025-12-26 22:22:14 +08:00
谦君
94fa2ad57a feat: 多语言完善造船厂与研究相关字段
为de、en、es-LA、ko、ru、zh-CN、zh-TW等多语言文件补充和完善造船厂(shipyard)与研究(research)相关字段,包括攻击、防御、装甲、建造成本、总成本、批量计算等,提升界面一致性与本地化体验。同时优化通知弹窗滚动区域样式,增加overflow-y-auto,提升内容自适应性。
2025-12-26 01:53:19 +08:00
谦君
7ed508945a build: Android版本号自动同步package.json
android/app/build.gradle中通过读取package.json自动设置versionName与versionCode,实现前后端版本号一致,避免手动同步出错。
2025-12-26 01:16:08 +08:00
谦君
fe2e5bfad9 refactor: 优化ResourceIcon样式及兼容性
将ResourceIcon根元素由div改为span,调整样式为inline-block和shrink-0,提升布局灵活性。颜色由渐变改为纯色背景,增强在Android WebView等环境下的显示兼容性。尺寸样式增加min-width/min-height,确保图标不被压缩。
2025-12-25 21:29:38 +08:00
谦君
7f36b6693f style: 优化通知弹窗滚动区域高度样式
将DiplomaticNotifications、EnemyAlertNotifications与QueueNotifications中的ScrollArea高度由固定h-96/h-[420px]调整为h-auto max-h-96,提升内容自适应性,避免内容较少时出现多余空白,增强界面美观与一致性。
2025-12-25 21:17:53 +08:00
谦君
27d60ae71a fix: 禁用WebView文本缩放并修复Portal定位
安卓端MainActivity中强制WebView文本缩放为100%,防止系统字体大小影响布局。capacitor.config.ts同步禁用WebView文本缩放及键盘视口调整。CSS中统一禁用文本大小调整,修复Edge-to-Edge模式下Portal容器定位问题,提升移动端显示一致性。
2025-12-25 20:40:02 +08:00
谦君
ca1aed1e9b style: 优化可滚动Dialog内容与遮罩布局
ScrollableDialogContent重构遮罩与内容结构,遮罩层支持flex居中与内边距,内容容器样式与DialogContent统一,提升弹窗显示一致性与居中效果。DialogContent补充relative定位,便于后续扩展。
2025-12-25 20:12:01 +08:00
谦君
04ee72a33d feat: 安卓端支持沉浸式边到边显示
MainActivity启用Edge-to-Edge,状态栏与导航栏设为透明并强制深色图标,提升沉浸体验。styles.xml同步调整相关颜色为透明。CSS中优化html平滑过渡样式,提升界面切换流畅度。
2025-12-25 20:00:13 +08:00
谦君
d95dffcfcd style: 优化Dialog与AlertDialog居中与间距样式
调整AlertDialogContent、DialogContent及DialogOverlay的布局样式,统一弹窗居中方式,增加flex居中与padding,提升弹窗在不同屏幕下的显示效果与一致性。
2025-12-25 19:51:15 +08:00
谦君
b6bcae3294 fix: 统一APK文件扩展名为小写.apk
将构建产物及相关CI流程中的APK文件扩展名由大写.APK统一为小写.apk,提升平台兼容性并避免文件识别问题。
2025-12-25 19:26:44 +08:00
谦君
ebed10b82f feat: 优化Dialog内容样式并完善多语言“建造”文案
调整AlertDialogContent与DialogContent的宽度与定位样式,提升弹窗显示效果。多语言文件中buildingsView部分新增“build”字段,完善德语、英语、韩语、俄语、简体中文的“建造”相关文案,提升界面一致性与本地化体验。
2025-12-25 19:24:11 +08:00
谦君
f4f5a719f5 ci: 构建流程切换为官方Gradle Action
将原有自定义Gradle缓存步骤替换为gradle/actions/setup-gradle官方Action,简化配置并利用内置智能缓存,提升CI流程维护性与稳定性。
2025-12-25 18:46:52 +08:00
谦君
1686622013 chore: 优化CI缓存与YAML格式统一
构建流程中Gradle缓存新增build-cache目录,并在assembleRelease时启用--build-cache参数,提升构建效率。统一GitHub Actions YAML文件中分支、标签、条件判断等格式,增强可读性与一致性。
2025-12-25 18:38:54 +08:00
47 changed files with 1000 additions and 716 deletions

View File

@@ -72,16 +72,11 @@ jobs:
- name: Setup Android SDK - name: Setup Android SDK
uses: android-actions/setup-android@v3 uses: android-actions/setup-android@v3
# 缓存 Gradle 依赖 (可节省 3-5 分钟) # 使用官方 Gradle Action内置智能缓存
- name: Cache Gradle - name: Setup Gradle
uses: actions/cache@v4 uses: gradle/actions/setup-gradle@v4
with: with:
path: | cache-read-only: false
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('android/**/*.gradle*', 'android/**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
- name: Build Frontend - name: Build Frontend
run: | run: |
@@ -118,13 +113,13 @@ jobs:
working-directory: android working-directory: android
run: | run: |
chmod +x ./gradlew chmod +x ./gradlew
./gradlew assembleRelease --no-daemon ./gradlew assembleRelease --build-cache
- name: Upload APK Artifacts - name: Upload APK Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: android-apk name: android-apk
path: android/app/build/outputs/apk/release/*.APK path: android/app/build/outputs/apk/release/*.apk
# 3. 构建 Electron 客户端 # 3. 构建 Electron 客户端
build-electron: build-electron:
@@ -180,7 +175,7 @@ jobs:
# 4. 发布 Release # 4. 发布 Release
release: release:
needs: [ build-server, build-android, build-electron ] needs: [build-server, build-android, build-electron]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
@@ -211,7 +206,7 @@ jobs:
find ./raw-assets/electron-* -type f \( -name "*.exe" -o -name "*.dmg" -o -name "*.AppImage" -o -name "*.zip" \) -exec cp {} ./final-release/ \; find ./raw-assets/electron-* -type f \( -name "*.exe" -o -name "*.dmg" -o -name "*.AppImage" -o -name "*.zip" \) -exec cp {} ./final-release/ \;
# 移动 Android APK # 移动 Android APK
find ./raw-assets/android-apk -type f -name "*.APK" -exec cp {} ./final-release/ \; || true find ./raw-assets/android-apk -type f -name "*.apk" -exec cp {} ./final-release/ \; || true
# 检查结果 # 检查结果
echo "Final assets to upload:" echo "Final assets to upload:"
@@ -222,7 +217,7 @@ jobs:
run: | run: |
VERSION=${{ steps.get_version.outputs.VERSION }} VERSION=${{ steps.get_version.outputs.VERSION }}
# 获取 release 中的现有 assets 并删除 APK 文件 # 获取 release 中的现有 assets 并删除 APK 文件
gh release view "$VERSION" --json assets -q '.assets[].name' 2>/dev/null | grep -i '\.APK$' | while read asset; do gh release view "$VERSION" --json assets -q '.assets[].name' 2>/dev/null | grep -i '\.apk$' | while read asset; do
echo "Deleting existing asset: $asset" echo "Deleting existing asset: $asset"
gh release delete-asset "$VERSION" "$asset" -y || true gh release delete-asset "$VERSION" "$asset" -y || true
done done

View File

@@ -2,7 +2,7 @@ name: 构建 Github Pages
on: on:
push: push:
branches: [ main ] # 如果你的主分支叫 master请改为 master branches: [main] # 如果你的主分支叫 master请改为 master
permissions: permissions:
contents: read contents: read

View File

@@ -2,8 +2,8 @@ name: Docker 多架构构建并发布
on: on:
push: push:
branches: [ main ] branches: [main]
tags: [ 'v*.*.*' ] # 打 tag 时也触发 tags: ['v*.*.*'] # 打 tag 时也触发
workflow_dispatch: workflow_dispatch:
permissions: permissions:

View File

@@ -1,5 +1,15 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
// 从 package.json 读取版本号
def packageJsonFile = file('../../package.json')
def packageJsonText = packageJsonFile.text
// 使用正则提取版本号
def versionMatcher = packageJsonText =~ /"version"\s*:\s*"([^"]+)"/
def appVersionName = versionMatcher ? versionMatcher[0][1] : "1.0.0"
// 将版本号转换为 versionCode例如 "1.5.5" -> 1*10000 + 5*100 + 5 = 10505
def versionParts = appVersionName.split('\\.')
def appVersionCode = versionParts[0].toInteger() * 10000 + versionParts[1].toInteger() * 100 + versionParts[2].toInteger()
android { android {
namespace = "games.wenzi.ogame" namespace = "games.wenzi.ogame"
compileSdk = rootProject.ext.compileSdkVersion compileSdk = rootProject.ext.compileSdkVersion
@@ -7,8 +17,8 @@ android {
applicationId "games.wenzi.ogame" applicationId "games.wenzi.ogame"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 15 versionCode appVersionCode
versionName "1.5.0" versionName appVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions { aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -56,7 +66,7 @@ android {
def abi = output.getFilter(com.android.build.OutputFile.ABI) def abi = output.getFilter(com.android.build.OutputFile.ABI)
if (abi != null) { if (abi != null) {
output.versionCodeOverride = abiVersionCode[abi] * 1000 + defaultConfig.versionCode output.versionCodeOverride = abiVersionCode[abi] * 1000 + defaultConfig.versionCode
output.outputFileName = "OGame-Vue-Ts-${abi}.APK" output.outputFileName = "OGame-Vue-Ts-${abi}.apk"
} }
} }
} }

View File

@@ -9,7 +9,8 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies { dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-filesystem')
} }

View File

@@ -8,6 +8,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:hardwareAccelerated="true"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">

View File

@@ -1,18 +1,100 @@
package games.wenzi.ogame; package games.wenzi.ogame;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.view.Window; import android.view.Window;
import androidx.core.content.ContextCompat; import android.view.animation.AccelerateDecelerateInterpolator;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.core.splashscreen.SplashScreen;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import com.getcapacitor.BridgeActivity; import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity { public class MainActivity extends BridgeActivity {
private boolean isWebViewReady = false;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
// 安装 SplashScreen必须在 super.onCreate 之前调用
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
// 保持 SplashScreen 直到 WebView 加载完成
splashScreen.setKeepOnScreenCondition(() -> !isWebViewReady);
// 设置淡出退出动画
splashScreen.setOnExitAnimationListener(splashScreenView -> {
// 创建淡出动画
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(
splashScreenView.getView(),
View.ALPHA,
1f,
0f
);
fadeOut.setInterpolator(new AccelerateDecelerateInterpolator());
fadeOut.setDuration(300);
// 动画结束后移除 SplashScreen
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
splashScreenView.remove();
}
});
fadeOut.start();
});
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// 设置状态栏颜色,防止 Capacitor 强制透明
Window window = getWindow(); Window window = getWindow();
window.setStatusBarColor(ContextCompat.getColor(this, R.color.status_bar_color));
window.setNavigationBarColor(ContextCompat.getColor(this, R.color.status_bar_color)); // 启用边到边显示Edge-to-Edge
WindowCompat.setDecorFitsSystemWindows(window, false);
// 设置透明状态栏和导航栏
window.setStatusBarColor(Color.TRANSPARENT);
window.setNavigationBarColor(Color.TRANSPARENT);
// 设置状态栏图标为浅色(因为背景是深色)
WindowInsetsControllerCompat controller = WindowCompat.getInsetsController(window, window.getDecorView());
if (controller != null) {
controller.setAppearanceLightStatusBars(false);
controller.setAppearanceLightNavigationBars(false);
}
}
@Override
public void onStart() {
super.onStart();
WebView webView = getBridge().getWebView();
if (webView != null) {
WebSettings settings = webView.getSettings();
// 禁用 WebView 文本缩放,防止系统字体大小设置影响布局
settings.setTextZoom(100);
// 优化 WebView 性能
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
settings.setDomStorageEnabled(true);
settings.setDatabaseEnabled(true);
// 启用硬件加速渲染
webView.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null);
// 监听页面加载进度,加载完成后隐藏 SplashScreen
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
// 当页面加载达到 80% 时认为可以显示
if (newProgress >= 80) {
isWebViewReady = true;
}
}
});
}
} }
} }

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 启动画面专用图标,使用较小的尺寸避免模糊 -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:width="96dp"
android:height="96dp"
android:gravity="center"
android:drawable="@mipmap/ic_launcher_foreground" />
</layer-list>

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="status_bar_color">#1a1a2e</color> <color name="status_bar_color">#1a1a2e</color>
<color name="splash_background">#0f0f1a</color>
</resources> </resources>

View File

@@ -13,12 +13,21 @@
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
<item name="android:background">@null</item> <item name="android:background">@null</item>
<item name="android:statusBarColor">#1a1a2e</item> <item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">#1a1a2e</item> <item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowTranslucentNavigation">false</item>
</style> </style>
<!-- 启动画面主题 - 延长显示直到 WebView 加载完成 -->
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen"> <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item> <item name="windowSplashScreenBackground">@color/splash_background</item>
<!-- 使用较小的自定义图标避免模糊 -->
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
<!-- 图标大小限制 -->
<item name="windowSplashScreenIconBackgroundColor">@color/splash_background</item>
<!-- 退出动画时长 -->
<item name="android:windowSplashScreenAnimationDuration">500</item>
<item name="postSplashScreenTheme">@style/AppTheme.NoActionBar</item>
</style> </style>
</resources> </resources>

View File

@@ -1,3 +1,9 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@8.0.0_@capacitor+core@8.0.0/node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@8.0.0_@capacitor+core@8.0.0/node_modules/@capacitor/android/capacitor')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacitor+app@8.0.0_@capacitor+core@8.0.0/node_modules/@capacitor/app/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/.pnpm/@capacitor+filesystem@8.0.0_@capacitor+core@8.0.0/node_modules/@capacitor/filesystem/android')

View File

@@ -5,12 +5,22 @@ const config: CapacitorConfig = {
appName: 'OGame Vue Ts', appName: 'OGame Vue Ts',
webDir: 'docs', webDir: 'docs',
server: { server: {
androidScheme: 'https' androidScheme: 'https',
cacheControl: 'max-age=31536000'
}, },
android: { android: {
buildOptions: { buildOptions: {
keystorePath: undefined, keystorePath: undefined,
keystoreAlias: undefined keystoreAlias: undefined
},
webContentsDebuggingEnabled: false,
allowMixedContent: false,
hardwareAcceleration: true
},
plugins: {
// 禁用键盘自动调整视口
Keyboard: {
resize: 'none'
} }
} }
} }

View File

@@ -6,6 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" /> <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" /> <link rel="icon" type="image/svg+xml" href="/logo.svg" />
<link rel="apple-touch-icon" href="/logo.svg" />
<title>OGame-Vue-Ts</title> <title>OGame-Vue-Ts</title>
</head> </head>
@@ -13,8 +14,9 @@
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
<!-- 统计勿删 --> <!-- 统计勿删 -->
<script charset="UTF-8" id="LA_COLLECT" src="https://sdk.51.la/js-sdk-pro.min.js"></script> <script>
<script>LA.init({ id: "L298GYqn6JhAO0VU", ck: "L298GYqn6JhAO0VU", autoTrack: true, hashMode: true })</script> !function (p) { "use strict"; !function (t) { var s = window, e = document, i = p, c = "".concat("https:" === e.location.protocol ? "https://" : "http://", "sdk.51.la/js-sdk-pro.min.js"), n = e.createElement("script"), r = e.getElementsByTagName("script")[0]; n.type = "text/javascript", n.setAttribute("charset", "UTF-8"), n.async = !0, n.src = c, n.id = "LA_COLLECT", i.d = n; var o = function () { s.LA.ids.push(i) }; s.LA ? s.LA.ids && o() : (s.LA = p, s.LA.ids = [], o()), r.parentNode.insertBefore(n, r) }() }({ id: "L298GYqn6JhAO0VU", ck: "L298GYqn6JhAO0VU", autoTrack: true, hashMode: true });
</script>
</body> </body>
</html> </html>

View File

@@ -8,8 +8,8 @@
"email": "1962257451@qq.com" "email": "1962257451@qq.com"
}, },
"private": true, "private": true,
"version": "1.5.5", "version": "1.5.6",
"buildDate": "2025/12/25 18:23:43", "buildDate": "2025/12/27 03:58:44",
"main": "dist-electron/main.js", "main": "dist-electron/main.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -44,7 +44,8 @@
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.17",
"vue": "^3.5.24", "vue": "^3.5.24",
"vue-router": "4", "vue-router": "4",
"vue-sonner": "^2.0.9" "vue-sonner": "^2.0.9",
"lightningcss": "^1.30.2"
}, },
"devDependencies": { "devDependencies": {
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
@@ -52,7 +53,6 @@
"@types/node": "^24.10.2", "@types/node": "^24.10.2",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1", "@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.23",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",

32
pnpm-lock.yaml generated
View File

@@ -44,6 +44,9 @@ importers:
finalhandler: finalhandler:
specifier: ^2.1.1 specifier: ^2.1.1
version: 2.1.1 version: 2.1.1
lightningcss:
specifier: ^1.30.2
version: 1.30.2
lucide-vue-next: lucide-vue-next:
specifier: ^0.556.0 specifier: ^0.556.0
version: 0.556.0(vue@3.5.25(typescript@5.9.3)) version: 0.556.0(vue@3.5.25(typescript@5.9.3))
@@ -93,9 +96,6 @@ importers:
'@vue/tsconfig': '@vue/tsconfig':
specifier: ^0.8.1 specifier: ^0.8.1
version: 0.8.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)) version: 0.8.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))
autoprefixer:
specifier: ^10.4.23
version: 10.4.23(postcss@8.5.6)
class-variance-authority: class-variance-authority:
specifier: ^0.7.1 specifier: ^0.7.1
version: 0.7.1 version: 0.7.1
@@ -1672,13 +1672,6 @@ packages:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
engines: {node: '>= 4.0.0'} engines: {node: '>= 4.0.0'}
autoprefixer@10.4.23:
resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
peerDependencies:
postcss: ^8.1.0
available-typed-arrays@1.0.7: available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -2215,9 +2208,6 @@ packages:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
framer-motion@12.23.12: framer-motion@12.23.12:
resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==} resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==}
peerDependencies: peerDependencies:
@@ -3114,9 +3104,6 @@ packages:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
postcss@8.5.6: postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@@ -5680,15 +5667,6 @@ snapshots:
at-least-node@1.0.0: {} at-least-node@1.0.0: {}
autoprefixer@10.4.23(postcss@8.5.6):
dependencies:
browserslist: 4.28.1
caniuse-lite: 1.0.30001760
fraction.js: 5.3.4
picocolors: 1.1.1
postcss: 8.5.6
postcss-value-parser: 4.2.0
available-typed-arrays@1.0.7: available-typed-arrays@1.0.7:
dependencies: dependencies:
possible-typed-array-names: 1.1.0 possible-typed-array-names: 1.1.0
@@ -6395,8 +6373,6 @@ snapshots:
hasown: 2.0.2 hasown: 2.0.2
mime-types: 2.1.35 mime-types: 2.1.35
fraction.js@5.3.4: {}
framer-motion@12.23.12: framer-motion@12.23.12:
dependencies: dependencies:
motion-dom: 12.23.12 motion-dom: 12.23.12
@@ -7267,8 +7243,6 @@ snapshots:
possible-typed-array-names@1.1.0: {} possible-typed-array-names@1.1.0: {}
postcss-value-parser@4.2.0: {}
postcss@8.5.6: postcss@8.5.6:
dependencies: dependencies:
nanoid: 3.3.11 nanoid: 3.3.11

View File

@@ -1,8 +1,6 @@
<template> <template>
<!-- 首页无侧边栏/头部 --> <!-- 首页无侧边栏/头部 -->
<template v-if="isHomePage"> <RouterView v-if="isHomePage" />
<RouterView />
</template>
<!-- 其他页面完整布局含侧边栏 --> <!-- 其他页面完整布局含侧边栏 -->
<SidebarProvider v-else :open="sidebarOpen" @update:open="handleSidebarOpenChange"> <SidebarProvider v-else :open="sidebarOpen" @update:open="handleSidebarOpenChange">
@@ -133,13 +131,6 @@
<SidebarMenuBadge v-if="item.path === '/fleet' && activeFleetMissionsCount > 0" class="bg-primary text-primary-foreground"> <SidebarMenuBadge v-if="item.path === '/fleet' && activeFleetMissionsCount > 0" class="bg-primary text-primary-foreground">
{{ activeFleetMissionsCount }} {{ activeFleetMissionsCount }}
</SidebarMenuBadge> </SidebarMenuBadge>
<!-- 未读外交报告数量 -->
<SidebarMenuBadge
v-if="item.path === '/diplomacy' && unreadDiplomaticReportsCount > 0"
class="bg-destructive text-destructive-foreground"
>
{{ unreadDiplomaticReportsCount }}
</SidebarMenuBadge>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
</SidebarMenu> </SidebarMenu>
@@ -738,11 +729,6 @@
return fleetMissions + flyingMissiles return fleetMissions + flyingMissiles
}) })
// 未读外交报告数量
const unreadDiplomaticReportsCount = computed(() => {
return (gameStore.player.diplomaticReports || []).filter(r => !r.read).length
})
// 月球相关 // 月球相关
const moon = computed(() => { const moon = computed(() => {
if (!planet.value || planet.value.isMoon) return null if (!planet.value || planet.value.isMoon) return null

View File

@@ -1,6 +1,12 @@
<template> <template>
<Dialog :open="true" @update:open="handleClose"> <Dialog :open="true" @update:open="handleClose">
<DialogContent class="max-w-2xl p-0 overflow-hidden bg-gradient-to-b from-background to-background/95"> <DialogContent class="max-w-2xl p-0 overflow-hidden bg-gradient-to-b from-background to-background/95">
<!-- 可访问性标题隐藏 -->
<VisuallyHidden>
<DialogTitle>{{ t('campaign.dialogue.title') }}</DialogTitle>
<DialogDescription>{{ t('campaign.dialogue.description') }}</DialogDescription>
</VisuallyHidden>
<!-- 对话框头部 - 星空效果 --> <!-- 对话框头部 - 星空效果 -->
<div class="absolute inset-0 pointer-events-none overflow-hidden"> <div class="absolute inset-0 pointer-events-none overflow-hidden">
<div class="stars-bg" /> <div class="stars-bg" />
@@ -80,7 +86,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue' import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { useI18n } from '@/composables/useI18n' import { useI18n } from '@/composables/useI18n'
import { Dialog, DialogContent } from '@/components/ui/dialog' import { Dialog, DialogContent, DialogTitle, DialogDescription } from '@/components/ui/dialog'
import { VisuallyHidden } from 'reka-ui'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import type { StoryDialogue, DialogueChoice } from '@/types/game' import type { StoryDialogue, DialogueChoice } from '@/types/game'
import { User, Bot, HelpCircle, MessageCircle, ChevronRight } from 'lucide-vue-next' import { User, Bot, HelpCircle, MessageCircle, ChevronRight } from 'lucide-vue-next'

View File

@@ -1,5 +1,5 @@
<template> <template>
<div :class="[colors[type], sizes[size], 'rounded shadow-sm']" /> <span :class="[colors[type], sizes[size], 'inline-block rounded shrink-0']" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -8,21 +8,22 @@
size?: 'sm' | 'md' | 'lg' size?: 'sm' | 'md' | 'lg'
} }
const props = withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
size: 'md' size: 'md'
}) })
// 使用纯色背景,在 Android WebView 中更可靠
const colors = { const colors = {
metal: 'bg-gradient-to-br from-slate-400 to-slate-600', metal: 'bg-slate-500',
crystal: 'bg-gradient-to-br from-cyan-400 to-blue-600', crystal: 'bg-cyan-500',
deuterium: 'bg-gradient-to-br from-green-400 to-emerald-600', deuterium: 'bg-green-500',
darkMatter: 'bg-gradient-to-br from-purple-600 to-indigo-900', darkMatter: 'bg-purple-700',
energy: 'bg-gradient-to-br from-yellow-400 to-orange-500' energy: 'bg-yellow-500'
} }
const sizes = { const sizes = {
sm: 'w-3 h-3', sm: 'w-3 h-3 min-w-3 min-h-3',
md: 'w-4 h-4', md: 'w-4 h-4 min-w-4 min-h-4',
lg: 'w-5 h-5' lg: 'w-5 h-5 min-w-5 min-h-5'
} }
</script> </script>

View File

@@ -19,7 +19,7 @@
{{ t('diplomacy.markAllRead') }} {{ t('diplomacy.markAllRead') }}
</Button> </Button>
</div> </div>
<ScrollArea class="h-96"> <ScrollArea class="h-auto max-h-96 overflow-y-auto">
<Empty v-if="allNotifications.length === 0" class="border-0"> <Empty v-if="allNotifications.length === 0" class="border-0">
<EmptyContent> <EmptyContent>
<ScrollText class="h-10 w-10 text-muted-foreground" /> <ScrollText class="h-10 w-10 text-muted-foreground" />

View File

@@ -19,7 +19,7 @@
{{ t('enemyAlert.markAllRead') }} {{ t('enemyAlert.markAllRead') }}
</Button> </Button>
</div> </div>
<ScrollArea class="h-96"> <ScrollArea class="h-auto max-h-96 overflow-y-auto">
<Empty v-if="activeAlerts.length === 0" class="border-0"> <Empty v-if="activeAlerts.length === 0" class="border-0">
<EmptyContent> <EmptyContent>
<Shield class="h-10 w-10 text-muted-foreground" /> <Shield class="h-10 w-10 text-muted-foreground" />

View File

@@ -32,7 +32,7 @@
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<ScrollArea class="h-[420px]"> <ScrollArea class="h-auto max-h-96 overflow-y-auto">
<TabsContent v-for="tab in tabConfig" :key="tab.value" :value="tab.value" class="mt-0"> <TabsContent v-for="tab in tabConfig" :key="tab.value" :value="tab.value" class="mt-0">
<Empty v-if="tab.items.length === 0" class="border-0"> <Empty v-if="tab.items.length === 0" class="border-0">
<EmptyContent> <EmptyContent>
@@ -63,7 +63,9 @@
class="text-[10px] sm:text-xs whitespace-nowrap" class="text-[10px] sm:text-xs whitespace-nowrap"
:class="isWaitingItemResourcesReady(item as WaitingQueueItem) ? 'text-green-500' : 'text-yellow-500'" :class="isWaitingItemResourcesReady(item as WaitingQueueItem) ? 'text-green-500' : 'text-yellow-500'"
> >
{{ isWaitingItemResourcesReady(item as WaitingQueueItem) ? t('queue.resourcesReady') : t('queue.waitingResources') }} {{
isWaitingItemResourcesReady(item as WaitingQueueItem) ? t('queue.resourcesReady') : t('queue.waitingResources')
}}
</span> </span>
<Button <Button
variant="ghost" variant="ghost"
@@ -146,8 +148,8 @@
import { useI18n } from '@/composables/useI18n' import { useI18n } from '@/composables/useI18n'
import { formatTime, formatNumber } from '@/utils/format' import { formatTime, formatNumber } from '@/utils/format'
import type { BuildQueueItem, WaitingQueueItem, BuildingType, ShipType, DefenseType, TechnologyType, Resources } from '@/types/game' import type { BuildQueueItem, WaitingQueueItem, BuildingType, ShipType, DefenseType, TechnologyType, Resources } from '@/types/game'
import * as waitingQueueLogic from '@/logic/waitingQueueLogic' import * as waitingQueueLogic from '@/logic/waitingQueueLogic'
import * as resourceLogic from '@/logic/resourceLogic' import * as resourceLogic from '@/logic/resourceLogic'
const { t } = useI18n() const { t } = useI18n()
const gameStore = useGameStore() const gameStore = useGameStore()

View File

@@ -81,6 +81,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useI18n } from '@/composables/useI18n' import { useI18n } from '@/composables/useI18n'
import { useGameStore } from '@/stores/gameStore'
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -95,13 +96,11 @@ import { Label } from '@/components/ui/label'
import { Loader2 } from 'lucide-vue-next' import { Loader2 } from 'lucide-vue-next'
import { import {
type WebDAVConfig, type WebDAVConfig,
getWebDAVConfig,
saveWebDAVConfig,
clearWebDAVConfig,
testWebDAVConnection testWebDAVConnection
} from '@/services/webdavService' } from '@/services/webdavService'
const { t } = useI18n() const { t } = useI18n()
const gameStore = useGameStore()
const isOpen = defineModel<boolean>('open', { default: false }) const isOpen = defineModel<boolean>('open', { default: false })
@@ -130,7 +129,7 @@ const isConfigValid = computed(() => {
// 加载已保存的配置 // 加载已保存的配置
watch(isOpen, (open) => { watch(isOpen, (open) => {
if (open) { if (open) {
const saved = getWebDAVConfig() const saved = gameStore.webdavConfig
if (saved) { if (saved) {
config.value = { ...saved } config.value = { ...saved }
hasExistingConfig.value = true hasExistingConfig.value = true
@@ -152,21 +151,25 @@ const handleTest = async () => {
testResult.value = null testResult.value = null
try { try {
testResult.value = await testWebDAVConnection(config.value) const result = await testWebDAVConnection(config.value)
testResult.value = {
success: result.success,
message: t(result.messageKey)
}
} finally { } finally {
isTesting.value = false isTesting.value = false
} }
} }
const handleSave = () => { const handleSave = () => {
saveWebDAVConfig(config.value) gameStore.webdavConfig = { ...config.value }
hasExistingConfig.value = true hasExistingConfig.value = true
emit('saved', config.value) emit('saved', config.value)
isOpen.value = false isOpen.value = false
} }
const handleClear = () => { const handleClear = () => {
clearWebDAVConfig() gameStore.webdavConfig = null
hasExistingConfig.value = false hasExistingConfig.value = false
config.value = { config.value = {
serverUrl: '', serverUrl: '',

View File

@@ -121,7 +121,7 @@ const loadFiles = async () => {
if (result.success && result.files) { if (result.success && result.files) {
files.value = result.files files.value = result.files
} else { } else {
error.value = result.message || t('settings.webdav.loadFailed') error.value = t(result.messageKey) || t('settings.webdav.loadFailed')
} }
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : String(e) error.value = e instanceof Error ? e.message : String(e)
@@ -174,7 +174,7 @@ const handleDelete = async (fileName: string) => {
selectedFile.value = null selectedFile.value = null
} }
} else { } else {
toast.error(result.message || t('settings.webdav.deleteFailed')) toast.error(t(result.messageKey) || t('settings.webdav.deleteFailed'))
} }
} }
</script> </script>

View File

@@ -2,20 +2,21 @@
<AlertDialogPortal> <AlertDialogPortal>
<AlertDialogOverlay <AlertDialogOverlay
data-slot="alert-dialog-overlay" data-slot="alert-dialog-overlay"
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80" class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4"
/> >
<AlertDialogContent <AlertDialogContent
data-slot="alert-dialog-content" data-slot="alert-dialog-content"
v-bind="{ ...$attrs, ...forwarded }" v-bind="{ ...$attrs, ...forwarded }"
:class=" :class="
cn( cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg', 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-50 grid w-full max-w-lg gap-4 rounded-lg border p-6 shadow-lg duration-200',
props.class props.class
) )
" "
> >
<slot /> <slot />
</AlertDialogContent> </AlertDialogContent>
</AlertDialogOverlay>
</AlertDialogPortal> </AlertDialogPortal>
</template> </template>

View File

@@ -1,12 +1,15 @@
<template> <template>
<DialogPortal> <DialogPortal>
<DialogOverlay /> <DialogOverlay
data-slot="dialog-overlay"
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-60 bg-black/80 flex items-center justify-center p-4"
>
<DialogContent <DialogContent
data-slot="dialog-content" data-slot="dialog-content"
v-bind="{ ...$attrs, ...forwarded }" v-bind="{ ...$attrs, ...forwarded }"
:class=" :class="
cn( cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-1/2 left-1/2 z-60 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-2xl', 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-60 grid w-full max-w-2xl gap-4 rounded-lg border p-6 shadow-lg duration-200 relative',
props.class props.class
) )
" "
@@ -22,6 +25,7 @@
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
</DialogClose> </DialogClose>
</DialogContent> </DialogContent>
</DialogOverlay>
</DialogPortal> </DialogPortal>
</template> </template>
@@ -30,9 +34,8 @@
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core' import { reactiveOmit } from '@vueuse/core'
import { X } from 'lucide-vue-next' import { X } from 'lucide-vue-next'
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui' import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import DialogOverlay from './DialogOverlay.vue'
defineOptions({ defineOptions({
inheritAttrs: false inheritAttrs: false

View File

@@ -4,7 +4,7 @@
v-bind="delegatedProps" v-bind="delegatedProps"
:class=" :class="
cn( cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-60 bg-black/80', 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-60 bg-black/80 flex items-center justify-center p-4',
props.class props.class
) )
" "

View File

@@ -1,12 +1,15 @@
<template> <template>
<DialogPortal> <DialogPortal>
<DialogOverlay /> <DialogOverlay
data-slot="dialog-overlay"
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-60 bg-black/80 flex items-center justify-center p-4"
>
<DialogContent <DialogContent
data-slot="scrollable-dialog-content" data-slot="scrollable-dialog-content"
v-bind="{ ...$attrs, ...forwarded }" v-bind="{ ...$attrs, ...forwarded }"
:class=" :class="
cn( cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-1/2 left-1/2 z-60 w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg border shadow-lg duration-200 sm:w-auto sm:min-w-[764px] flex flex-col p-0', 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-60 w-full max-w-3xl rounded-lg border shadow-lg duration-200 flex flex-col p-0 relative',
containerClass containerClass
) )
" "
@@ -36,6 +39,7 @@
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
</DialogClose> </DialogClose>
</DialogContent> </DialogContent>
</DialogOverlay>
</DialogPortal> </DialogPortal>
</template> </template>
@@ -44,9 +48,8 @@
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core' import { reactiveOmit } from '@vueuse/core'
import { X } from 'lucide-vue-next' import { X } from 'lucide-vue-next'
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui' import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import DialogOverlay from './DialogOverlay.vue'
defineOptions({ defineOptions({
inheritAttrs: false inheritAttrs: false

View File

@@ -157,6 +157,10 @@ export default {
build: 'Bauen', build: 'Bauen',
production: 'Produktion', production: 'Produktion',
consumption: 'Verbrauch', consumption: 'Verbrauch',
levelRange: 'Stufenbereich',
totalCost: 'Gesamtkosten',
totalPoints: 'Gesamtpunkte',
researchSpeedBonus: 'Forschungsgeschwindigkeitsbonus',
capacity: 'Capacity/Effect', capacity: 'Capacity/Effect',
storageCapacity: 'Capacity', storageCapacity: 'Capacity',
energyProduction: 'Energy Production', energyProduction: 'Energy Production',
@@ -265,6 +269,9 @@ export default {
}, },
research: { research: {
researchTime: 'Forschungszeit', researchTime: 'Forschungszeit',
levelRange: 'Stufenbereich',
totalCost: 'Gesamtkosten',
totalPoints: 'Gesamtpunkte',
capacity: 'Capacity/Effect', capacity: 'Capacity/Effect',
storageCapacity: 'Capacity', storageCapacity: 'Capacity',
energyProduction: 'Energy Production', energyProduction: 'Energy Production',
@@ -392,8 +399,10 @@ export default {
tabConsumption: 'Verbrauchsdetails' tabConsumption: 'Verbrauchsdetails'
}, },
buildingsView: { buildingsView: {
title: 'Gebäude',
spaceUsage: 'Platzbedarf', spaceUsage: 'Platzbedarf',
upgradeCost: 'Ausbaukosten', upgradeCost: 'Ausbaukosten',
build: 'Bauen',
upgrade: 'Ausbauen', upgrade: 'Ausbauen',
maxLevelReached: 'Maximale Stufe erreicht', maxLevelReached: 'Maximale Stufe erreicht',
requirementsNotMet: 'Anforderungen nicht erfüllt', requirementsNotMet: 'Anforderungen nicht erfüllt',
@@ -415,10 +424,20 @@ export default {
'Bitte überprüfen Sie, ob Sie genügend Ressourcen haben, die Voraussetzungen erfüllt sind oder keine anderen Forschungsaufträge vorhanden sind.' 'Bitte überprüfen Sie, ob Sie genügend Ressourcen haben, die Voraussetzungen erfüllt sind oder keine anderen Forschungsaufträge vorhanden sind.'
}, },
shipyard: { shipyard: {
attack: 'Angriff',
shield: 'Schild',
armor: 'Panzerung',
missileAttack: 'Raketenangriff', missileAttack: 'Raketenangriff',
speed: 'Geschwindigkeit', speed: 'Geschwindigkeit',
cargoCapacity: 'Ladekapazität', cargoCapacity: 'Ladekapazität',
fuelConsumption: 'Treibstoffverbrauch' fuelConsumption: 'Treibstoffverbrauch',
buildCost: 'Baukosten',
buildTime: 'Bauzeit',
perUnit: 'Pro Einheit',
batchCalculator: 'Stapelrechner',
quantity: 'Menge',
totalCost: 'Gesamtkosten',
totalTime: 'Gesamtzeit'
}, },
shipyardView: { shipyardView: {
title: 'Raumschiffwerft', title: 'Raumschiffwerft',
@@ -530,6 +549,8 @@ export default {
} }
}, },
recycle: 'Recyceln', recycle: 'Recyceln',
harvestDarkMatter: 'Dunkle Materie ernten',
station: 'Stationieren',
transportResources: 'Ressourcen transportieren', transportResources: 'Ressourcen transportieren',
totalCargoCapacity: 'Gesamtladekapazität', totalCargoCapacity: 'Gesamtladekapazität',
used: 'Verwendet', used: 'Verwendet',
@@ -1804,6 +1825,27 @@ export default {
reputationBonusDesc: 'Dein Verbündeter {npcName} spricht gut von dir zu {targetNpc}' reputationBonusDesc: 'Dein Verbündeter {npcName} spricht gut von dir zu {targetNpc}'
} }
}, },
webdav: {
connectionSuccess: 'WebDAV-Verbindung erfolgreich',
connectionSuccessDirectoryCreated: 'WebDAV-Verbindung erfolgreich, Speicherverzeichnis erstellt',
authFailed: 'Authentifizierung fehlgeschlagen, bitte Benutzername und Passwort überprüfen',
directoryNotExist: 'Verzeichnis existiert nicht und konnte nicht erstellt werden',
networkError: 'Netzwerkfehler, bitte Serveradresse und Netzwerk überprüfen',
unknownError: 'Unbekannter Fehler',
uploadSuccess: 'Spielstand erfolgreich hochgeladen',
uploadFailed: 'Hochladen fehlgeschlagen',
downloadSuccess: 'Spielstand erfolgreich heruntergeladen',
downloadFailed: 'Herunterladen fehlgeschlagen',
noSaveFiles: 'Keine Spielstände auf dem Server',
fileListSuccess: 'Spielstandliste erfolgreich abgerufen',
fileListFailed: 'Abrufen der Spielstandliste fehlgeschlagen',
deleteSuccess: 'Spielstand erfolgreich gelöscht',
deleteFailed: 'Löschen fehlgeschlagen',
serverError: 'Serverfehler',
notConfigured: 'WebDAV nicht konfiguriert',
invalidUrl: 'Ungültige WebDAV-URL',
timeout: 'Verbindungszeitüberschreitung'
},
campaign: { campaign: {
name: 'Kampagne', name: 'Kampagne',
description: 'Erkunde die mysteriöse Galaxie und entdecke antike Geheimnisse', description: 'Erkunde die mysteriöse Galaxie und entdecke antike Geheimnisse',
@@ -1830,6 +1872,8 @@ export default {
branchUnlocked: 'Neuer Storyzweig freigeschaltet!' branchUnlocked: 'Neuer Storyzweig freigeschaltet!'
}, },
dialogue: { dialogue: {
title: 'Story-Dialog',
description: 'Kampagnen-Story-Dialoginhalt',
skip: 'Überspringen', skip: 'Überspringen',
continue: 'Weiter', continue: 'Weiter',
finish: 'Beenden', finish: 'Beenden',
@@ -1942,25 +1986,42 @@ export default {
spyAnyNPC: 'Spioniere einen NPC-Planeten aus', spyAnyNPC: 'Spioniere einen NPC-Planeten aus',
sendGiftToNPC: 'Sende ein Geschenk an einen NPC', sendGiftToNPC: 'Sende ein Geschenk an einen NPC',
researchAstrophysics: 'Erforsche Astrophysik auf Stufe 1', researchAstrophysics: 'Erforsche Astrophysik auf Stufe 1',
researchAstrophysicsHigher: 'Erforsche Astrophysik auf Stufe 3',
buildColonyShip: 'Baue ein Kolonieschiff', buildColonyShip: 'Baue ein Kolonieschiff',
colonizeNewPlanet: 'Kolonisiere einen neuen Planeten', colonizeNewPlanet: 'Kolonisiere einen neuen Planeten',
completeExpedition: 'Schließe eine Expeditionsmission ab', colonizeMultiple: 'Kolonisiere 5 Planeten',
completeExpedition: 'Schließe 3 Expeditionsmissionen ab',
expeditionDeepSpace: 'Schließe 2 Tiefenraum-Expeditionen ab',
expeditionUncharted: 'Erkunde 1 unbekannte Region',
expeditionDangerous: 'Schließe 3 gefährliche Nebel-Expeditionen ab',
discoverRuins: 'Entdecke antike Ruinen', discoverRuins: 'Entdecke antike Ruinen',
researchComputer: 'Erforsche Computertechnik auf Stufe 4', researchComputer: 'Erforsche Computertechnik auf Stufe 4',
researchImpulse: 'Erforsche Impulsantrieb auf Stufe 3',
researchLaser: 'Erforsche Lasertechnik auf Stufe 5',
researchIntergalactic: 'Erforsche Computertechnik auf Stufe 10',
researchGraviton: 'Erforsche Gravitontechnik auf Stufe 1',
improveRelation: 'Verbessere Beziehungen zu einem NPC', improveRelation: 'Verbessere Beziehungen zu einem NPC',
reachFriendly: 'Erreiche freundlichen Status mit einem NPC', reachFriendly: 'Erreiche freundlichen Status mit einem NPC',
spyHostileNPC: 'Spioniere einen feindlichen NPC aus', reachFriendlyRelation: 'Erreiche freundlichen Status mit einem beliebigen NPC',
sendMultipleGifts: 'Sende 3 Geschenke an NPCs',
spyHostileNPC: 'Spioniere 2 feindliche NPCs aus',
formAlliance: 'Schließe Allianz mit einem freundlichen NPC', formAlliance: 'Schließe Allianz mit einem freundlichen NPC',
buildDefenses: 'Baue Verteidigungsanlagen', buildDefenses: 'Baue Verteidigungsanlagen',
buildMissileSilo: 'Baue Raketensilo auf Stufe 2',
buildCruisers: 'Baue 10 Kreuzer',
winDefenseBattle: 'Gewinne eine Verteidigungsschlacht', winDefenseBattle: 'Gewinne eine Verteidigungsschlacht',
defendAgainstAttack: 'Verteidige erfolgreich gegen 1 Angriff',
spyEnemyPlanet: 'Spioniere Feindplaneten aus', spyEnemyPlanet: 'Spioniere Feindplaneten aus',
spyEnemyPlanets: 'Spioniere 5 Feindplaneten aus',
winAttackBattles: 'Gewinne 3 Angriffsschlachten',
attackEnemy: 'Greife den Feind an', attackEnemy: 'Greife den Feind an',
recycleDebris: 'Recycel Trümmer', recycleDebris: 'Recycel 5 Mal Trümmer',
buildBattleships: 'Baue 10 Schlachtschiffe', buildBattleships: 'Baue 20 Schlachtschiffe',
exploreDeepRuins: 'Erkunde tiefe Ruinen', exploreDeepRuins: 'Erkunde tiefe Ruinen',
researchHyperspace: 'Erforsche Hyperraumtechnologie', researchHyperspace: 'Erforsche Hyperraumantrieb auf Stufe 3',
defeatBoss: 'Besiege den mysteriösen Feind', defeatBoss: 'Besiege den Antiken Wächter',
colonizeSpecial: 'Kolonisiere besonderen Standort', colonizeSpecial: 'Kolonisiere besonderen Standort',
accumulateWealth: 'Sammle 1 Million Gesamtressourcen',
continueDevelopment: 'Setze Entwicklung fort' continueDevelopment: 'Setze Entwicklung fort'
}, },
dialogues: { dialogues: {

View File

@@ -155,6 +155,10 @@ export default {
buildTime: 'Build Time', buildTime: 'Build Time',
production: 'Production', production: 'Production',
consumption: 'Consumption', consumption: 'Consumption',
levelRange: 'Level Range',
totalCost: 'Total Cost',
totalPoints: 'Total Points',
researchSpeedBonus: 'Research Speed Bonus',
storageCapacity: 'Capacity', storageCapacity: 'Capacity',
energyProduction: 'Energy Production', energyProduction: 'Energy Production',
@@ -260,6 +264,9 @@ export default {
}, },
research: { research: {
researchTime: 'Research Time', researchTime: 'Research Time',
levelRange: 'Level Range',
totalCost: 'Total Cost',
totalPoints: 'Total Points',
attackBonus: 'Attack Bonus', attackBonus: 'Attack Bonus',
shieldBonus: 'Shield Bonus', shieldBonus: 'Shield Bonus',
@@ -382,6 +389,7 @@ export default {
title: 'Buildings', title: 'Buildings',
spaceUsage: 'Space Usage', spaceUsage: 'Space Usage',
upgradeCost: 'Upgrade Cost', upgradeCost: 'Upgrade Cost',
build: 'Build',
upgrade: 'Upgrade', upgrade: 'Upgrade',
maxLevelReached: 'Max Level Reached', maxLevelReached: 'Max Level Reached',
requirementsNotMet: 'Requirements Not Met', requirementsNotMet: 'Requirements Not Met',
@@ -402,10 +410,20 @@ export default {
researchFailedMessage: 'Please check if you have enough resources, prerequisites are met, or if there are other research tasks.' researchFailedMessage: 'Please check if you have enough resources, prerequisites are met, or if there are other research tasks.'
}, },
shipyard: { shipyard: {
attack: 'Attack',
shield: 'Shield',
armor: 'Armor',
missileAttack: 'Missile Attack', missileAttack: 'Missile Attack',
speed: 'Speed', speed: 'Speed',
cargoCapacity: 'Cargo Capacity', cargoCapacity: 'Cargo Capacity',
fuelConsumption: 'Fuel Consumption' fuelConsumption: 'Fuel Consumption',
buildCost: 'Build Cost',
buildTime: 'Build Time',
perUnit: 'Per Unit',
batchCalculator: 'Batch Calculator',
quantity: 'Quantity',
totalCost: 'Total Cost',
totalTime: 'Total Time'
}, },
shipyardView: { shipyardView: {
title: 'Shipyard', title: 'Shipyard',
@@ -425,7 +443,17 @@ export default {
buildFailedMessage: 'Please check if you have enough resources or if prerequisites are met.' buildFailedMessage: 'Please check if you have enough resources or if prerequisites are met.'
}, },
defense: { defense: {
missileAttack: 'Missile Attack' attack: 'Attack',
shield: 'Shield',
armor: 'Armor',
missileAttack: 'Missile Attack',
buildCost: 'Build Cost',
buildTime: 'Build Time',
perUnit: 'Per Unit',
batchCalculator: 'Batch Calculator',
quantity: 'Quantity',
totalCost: 'Total Cost',
totalTime: 'Total Time'
}, },
defenseView: { defenseView: {
title: 'Defense', title: 'Defense',
@@ -504,6 +532,8 @@ export default {
}, },
recycle: 'Recycle', recycle: 'Recycle',
destroy: 'Planet Destruction', destroy: 'Planet Destruction',
harvestDarkMatter: 'Harvest Dark Matter',
station: 'Station',
transportResources: 'Transport Resources', transportResources: 'Transport Resources',
totalCargoCapacity: 'Total Cargo Capacity', totalCargoCapacity: 'Total Cargo Capacity',
used: 'Used', used: 'Used',
@@ -1753,6 +1783,8 @@ export default {
branchUnlocked: 'New story branch unlocked!' branchUnlocked: 'New story branch unlocked!'
}, },
dialogue: { dialogue: {
title: 'Story Dialogue',
description: 'Campaign story dialogue content',
skip: 'Skip', skip: 'Skip',
continue: 'Continue', continue: 'Continue',
finish: 'Finish', finish: 'Finish',
@@ -1940,25 +1972,42 @@ export default {
spyAnyNPC: 'Spy on any NPC planet', spyAnyNPC: 'Spy on any NPC planet',
sendGiftToNPC: 'Send a gift to any NPC', sendGiftToNPC: 'Send a gift to any NPC',
researchAstrophysics: 'Research Astrophysics to level 1', researchAstrophysics: 'Research Astrophysics to level 1',
researchAstrophysicsHigher: 'Research Astrophysics to level 3',
buildColonyShip: 'Build a Colony Ship', buildColonyShip: 'Build a Colony Ship',
colonizeNewPlanet: 'Colonize a new planet', colonizeNewPlanet: 'Colonize a new planet',
completeExpedition: 'Complete an expedition mission', colonizeMultiple: 'Colonize 5 planets',
completeExpedition: 'Complete 3 expedition missions',
expeditionDeepSpace: 'Complete 2 deep space expeditions',
expeditionUncharted: 'Explore 1 uncharted region',
expeditionDangerous: 'Complete 3 dangerous nebula expeditions',
discoverRuins: 'Discover ancient ruins', discoverRuins: 'Discover ancient ruins',
researchComputer: 'Research Computer Technology to level 4', researchComputer: 'Research Computer Technology to level 4',
researchImpulse: 'Research Impulse Drive to level 3',
researchLaser: 'Research Laser Technology to level 5',
researchIntergalactic: 'Research Computer Technology to level 10',
researchGraviton: 'Research Graviton Technology to level 1',
improveRelation: 'Improve relations with an NPC', improveRelation: 'Improve relations with an NPC',
reachFriendly: 'Reach friendly status with an NPC', reachFriendly: 'Reach friendly status with an NPC',
spyHostileNPC: 'Spy on a hostile NPC', reachFriendlyRelation: 'Reach friendly status with any NPC',
sendMultipleGifts: 'Send 3 gifts to NPCs',
spyHostileNPC: 'Spy on 2 hostile NPCs',
formAlliance: 'Form alliance with a friendly NPC', formAlliance: 'Form alliance with a friendly NPC',
buildDefenses: 'Build defense facilities', buildDefenses: 'Build defense facilities',
buildMissileSilo: 'Build Missile Silo to level 2',
buildCruisers: 'Build 10 Cruisers',
winDefenseBattle: 'Win a defensive battle', winDefenseBattle: 'Win a defensive battle',
defendAgainstAttack: 'Successfully defend against 1 attack',
spyEnemyPlanet: 'Spy on enemy planet', spyEnemyPlanet: 'Spy on enemy planet',
spyEnemyPlanets: 'Spy on 5 enemy planets',
winAttackBattles: 'Win 3 attack battles',
attackEnemy: 'Attack the enemy', attackEnemy: 'Attack the enemy',
recycleDebris: 'Recycle debris', recycleDebris: 'Recycle debris 5 times',
buildBattleships: 'Build 10 Battleships', buildBattleships: 'Build 20 Battleships',
exploreDeepRuins: 'Explore deep ruins', exploreDeepRuins: 'Explore deep ruins',
researchHyperspace: 'Research Hyperspace Technology', researchHyperspace: 'Research Hyperspace Drive to level 3',
defeatBoss: 'Defeat the mysterious enemy', defeatBoss: 'Defeat the Ancient Guardian',
colonizeSpecial: 'Colonize special location', colonizeSpecial: 'Colonize special location',
accumulateWealth: 'Accumulate 1 million total resources',
continueDevelopment: 'Continue development' continueDevelopment: 'Continue development'
}, },
dialogues: { dialogues: {
@@ -2162,5 +2211,31 @@ export default {
reputationBonus: 'Reputation Bonus', reputationBonus: 'Reputation Bonus',
reputationBonusDesc: 'Your ally {npcName} speaks well of you to {targetNpc}' reputationBonusDesc: 'Your ally {npcName} speaks well of you to {targetNpc}'
} }
},
webdav: {
// Connection
connectionSuccess: 'WebDAV connection successful',
connectionSuccessDirectoryCreated: 'WebDAV connection successful, save directory created',
authFailed: 'Authentication failed, please check username and password',
directoryNotExist: 'Directory does not exist and cannot be created',
connectionFailedHttp: 'Connection failed: HTTP {status}',
networkError: 'Network error, possibly CORS restriction. Try using a CORS-enabled WebDAV service or proxy.',
connectionError: 'Connection error: {error}',
// Upload
uploadSuccess: 'Upload successful',
noWritePermission: 'No write permission',
insufficientStorage: 'Insufficient storage space',
uploadFailedHttp: 'Upload failed: HTTP {status}',
uploadError: 'Upload error: {error}',
// Download
fileNotExist: 'File does not exist',
downloadFailedHttp: 'Download failed: HTTP {status}',
downloadError: 'Download error: {error}',
// List
listFailedHttp: 'Failed to get file list: HTTP {status}',
listError: 'Error getting file list: {error}',
// Delete
deleteFailedHttp: 'Delete failed: HTTP {status}',
deleteError: 'Delete error: {error}'
} }
} }

View File

@@ -155,6 +155,10 @@ export default {
buildTime: 'Tiempo de Construcción', buildTime: 'Tiempo de Construcción',
production: 'Producción', production: 'Producción',
consumption: 'Consumo', consumption: 'Consumo',
levelRange: 'Rango de Nivel',
totalCost: 'Costo Total',
totalPoints: 'Puntos Totales',
researchSpeedBonus: 'Bono de Velocidad de Investigación',
storageCapacity: 'Capacidad', storageCapacity: 'Capacidad',
energyProduction: 'Producción de Energía', energyProduction: 'Producción de Energía',
@@ -411,10 +415,20 @@ export default {
'Por favor verifica si tienes suficientes recursos, si se cumplen los prerrequisitos, o si hay otras tareas de investigación.' 'Por favor verifica si tienes suficientes recursos, si se cumplen los prerrequisitos, o si hay otras tareas de investigación.'
}, },
shipyard: { shipyard: {
attack: 'Ataque',
shield: 'Escudo',
armor: 'Blindaje',
missileAttack: 'Ataque de Misil', missileAttack: 'Ataque de Misil',
speed: 'Velocidad', speed: 'Velocidad',
cargoCapacity: 'Capacidad de Carga', cargoCapacity: 'Capacidad de Carga',
fuelConsumption: 'Consumo de Combustible' fuelConsumption: 'Consumo de Combustible',
buildCost: 'Costo de Construcción',
buildTime: 'Tiempo de Construcción',
perUnit: 'Por Unidad',
batchCalculator: 'Calculadora de Lotes',
quantity: 'Cantidad',
totalCost: 'Costo Total',
totalTime: 'Tiempo Total'
}, },
shipyardView: { shipyardView: {
title: 'Astillero', title: 'Astillero',
@@ -524,6 +538,8 @@ export default {
}, },
recycle: 'Reciclar', recycle: 'Reciclar',
destroy: 'Destrucción Planetaria', destroy: 'Destrucción Planetaria',
harvestDarkMatter: 'Recolectar Materia Oscura',
station: 'Estacionar',
transportResources: 'Transportar Recursos', transportResources: 'Transportar Recursos',
totalCargoCapacity: 'Capacidad de Carga Total', totalCargoCapacity: 'Capacidad de Carga Total',
used: 'Usado', used: 'Usado',
@@ -1771,6 +1787,8 @@ export default {
branchUnlocked: '¡Nueva rama de historia desbloqueada!' branchUnlocked: '¡Nueva rama de historia desbloqueada!'
}, },
dialogue: { dialogue: {
title: 'Diálogo de historia',
description: 'Contenido del diálogo de la campaña',
skip: 'Saltar', skip: 'Saltar',
continue: 'Continuar', continue: 'Continuar',
finish: 'Finalizar', finish: 'Finalizar',
@@ -1958,25 +1976,42 @@ export default {
spyAnyNPC: 'Espiar cualquier planeta NPC', spyAnyNPC: 'Espiar cualquier planeta NPC',
sendGiftToNPC: 'Enviar un regalo a cualquier NPC', sendGiftToNPC: 'Enviar un regalo a cualquier NPC',
researchAstrophysics: 'Investigar Astrofísica al nivel 1', researchAstrophysics: 'Investigar Astrofísica al nivel 1',
researchAstrophysicsHigher: 'Investigar Astrofísica al nivel 3',
buildColonyShip: 'Construir una Nave Colonial', buildColonyShip: 'Construir una Nave Colonial',
colonizeNewPlanet: 'Colonizar un nuevo planeta', colonizeNewPlanet: 'Colonizar un nuevo planeta',
completeExpedition: 'Completar una misión de expedición', colonizeMultiple: 'Colonizar 5 planetas',
completeExpedition: 'Completar 3 misiones de expedición',
expeditionDeepSpace: 'Completar 2 expediciones al espacio profundo',
expeditionUncharted: 'Explorar 1 región desconocida',
expeditionDangerous: 'Completar 3 expediciones a nebulosas peligrosas',
discoverRuins: 'Descubrir ruinas antiguas', discoverRuins: 'Descubrir ruinas antiguas',
researchComputer: 'Investigar Tecnología de Computación al nivel 4', researchComputer: 'Investigar Tecnología de Computación al nivel 4',
researchImpulse: 'Investigar Motor de Impulso al nivel 3',
researchLaser: 'Investigar Tecnología Láser al nivel 5',
researchIntergalactic: 'Investigar Tecnología de Computación al nivel 10',
researchGraviton: 'Investigar Tecnología Gravitón al nivel 1',
improveRelation: 'Mejorar relaciones con un NPC', improveRelation: 'Mejorar relaciones con un NPC',
reachFriendly: 'Alcanzar estatus amigable con un NPC', reachFriendly: 'Alcanzar estatus amigable con un NPC',
spyHostileNPC: 'Espiar un NPC hostil', reachFriendlyRelation: 'Alcanzar estatus amigable con cualquier NPC',
sendMultipleGifts: 'Enviar 3 regalos a NPCs',
spyHostileNPC: 'Espiar 2 NPCs hostiles',
formAlliance: 'Formar alianza con un NPC amigable', formAlliance: 'Formar alianza con un NPC amigable',
buildDefenses: 'Construir instalaciones de defensa', buildDefenses: 'Construir instalaciones de defensa',
buildMissileSilo: 'Construir Silo de Misiles al nivel 2',
buildCruisers: 'Construir 10 Cruceros',
winDefenseBattle: 'Ganar una batalla defensiva', winDefenseBattle: 'Ganar una batalla defensiva',
defendAgainstAttack: 'Defender exitosamente contra 1 ataque',
spyEnemyPlanet: 'Espiar planeta enemigo', spyEnemyPlanet: 'Espiar planeta enemigo',
spyEnemyPlanets: 'Espiar 5 planetas enemigos',
winAttackBattles: 'Ganar 3 batallas de ataque',
attackEnemy: 'Atacar al enemigo', attackEnemy: 'Atacar al enemigo',
recycleDebris: 'Reciclar escombros', recycleDebris: 'Reciclar escombros 5 veces',
buildBattleships: 'Construir 10 Naves de Batalla', buildBattleships: 'Construir 20 Naves de Batalla',
exploreDeepRuins: 'Explorar ruinas profundas', exploreDeepRuins: 'Explorar ruinas profundas',
researchHyperspace: 'Investigar Tecnología de Hiperespacio', researchHyperspace: 'Investigar Motor de Hiperespacio al nivel 3',
defeatBoss: 'Derrotar al enemigo misterioso', defeatBoss: 'Derrotar al Guardián Antiguo',
colonizeSpecial: 'Colonizar ubicación especial', colonizeSpecial: 'Colonizar ubicación especial',
accumulateWealth: 'Acumular 1 millón de recursos totales',
continueDevelopment: 'Continuar desarrollo' continueDevelopment: 'Continuar desarrollo'
}, },
dialogues: { dialogues: {
@@ -2184,5 +2219,26 @@ export default {
reputationBonus: 'Bonificación de Reputación', reputationBonus: 'Bonificación de Reputación',
reputationBonusDesc: 'Tu aliado {npcName} habla bien de ti ante {targetNpc}' reputationBonusDesc: 'Tu aliado {npcName} habla bien de ti ante {targetNpc}'
} }
},
webdav: {
connectionSuccess: 'Conexión WebDAV exitosa',
connectionSuccessDirectoryCreated: 'Conexión WebDAV exitosa, directorio de guardado creado',
authFailed: 'Autenticación fallida, verifica el nombre de usuario y contraseña',
directoryNotExist: 'El directorio no existe y no se puede crear',
networkError: 'Error de red, verifica la dirección del servidor y la red',
unknownError: 'Error desconocido',
uploadSuccess: 'Guardado subido exitosamente',
uploadFailed: 'Error al subir',
downloadSuccess: 'Guardado descargado exitosamente',
downloadFailed: 'Error al descargar',
noSaveFiles: 'No hay archivos de guardado en el servidor',
fileListSuccess: 'Lista de guardados obtenida exitosamente',
fileListFailed: 'Error al obtener la lista de guardados',
deleteSuccess: 'Guardado eliminado exitosamente',
deleteFailed: 'Error al eliminar',
serverError: 'Error del servidor',
notConfigured: 'WebDAV no configurado',
invalidUrl: 'URL de WebDAV inválida',
timeout: 'Tiempo de conexión agotado'
} }
} }

View File

@@ -641,7 +641,9 @@ export default {
jumpGateSuccessMessage: '艦隊は{target}へ瞬時に転送されました', jumpGateSuccessMessage: '艦隊は{target}へ瞬時に転送されました',
jumpGateFailed: 'ジャンプゲート転送失敗', jumpGateFailed: 'ジャンプゲート転送失敗',
jumpGateFailedMessage: 'ジャンプゲートの状態と艦隊構成を確認してください', jumpGateFailedMessage: 'ジャンプゲートの状態と艦隊構成を確認してください',
destroy: '破壊' destroy: '破壊',
harvestDarkMatter: 'ダークマター採取',
station: '駐留'
}, },
officersView: { officersView: {
title: '士官', title: '士官',
@@ -1854,6 +1856,27 @@ export default {
reputationBonusDesc: '同盟の{npcName}が{targetNpc}にあなたのことを良く言っています' reputationBonusDesc: '同盟の{npcName}が{targetNpc}にあなたのことを良く言っています'
} }
}, },
webdav: {
connectionSuccess: 'WebDAV接続成功',
connectionSuccessDirectoryCreated: 'WebDAV接続成功、保存ディレクトリを作成しました',
authFailed: '認証失敗、ユーザー名とパスワードを確認してください',
directoryNotExist: 'ディレクトリが存在せず、作成できませんでした',
networkError: 'ネットワークエラー、サーバーアドレスとネットワークを確認してください',
unknownError: '不明なエラー',
uploadSuccess: 'セーブデータのアップロード成功',
uploadFailed: 'アップロード失敗',
downloadSuccess: 'セーブデータのダウンロード成功',
downloadFailed: 'ダウンロード失敗',
noSaveFiles: 'サーバーにセーブデータがありません',
fileListSuccess: 'セーブデータリストの取得成功',
fileListFailed: 'セーブデータリストの取得失敗',
deleteSuccess: 'セーブデータの削除成功',
deleteFailed: '削除失敗',
serverError: 'サーバーエラー',
notConfigured: 'WebDAVが設定されていません',
invalidUrl: '無効なWebDAV URL',
timeout: '接続タイムアウト'
},
campaign: { campaign: {
name: 'キャンペーン', name: 'キャンペーン',
description: '神秘的な銀河を探索し、古代の秘密を解き明かす', description: '神秘的な銀河を探索し、古代の秘密を解き明かす',
@@ -1881,6 +1904,8 @@ export default {
branchUnlocked: '新しいストーリー分岐が解放されました!' branchUnlocked: '新しいストーリー分岐が解放されました!'
}, },
dialogue: { dialogue: {
title: 'ストーリー対話',
description: 'キャンペーンストーリーの対話内容',
skip: 'スキップ', skip: 'スキップ',
continue: '続ける', continue: '続ける',
finish: '完了', finish: '完了',
@@ -1993,25 +2018,42 @@ export default {
spyAnyNPC: 'NPC惑星をスパイ', spyAnyNPC: 'NPC惑星をスパイ',
sendGiftToNPC: 'NPCに贈り物を送る', sendGiftToNPC: 'NPCに贈り物を送る',
researchAstrophysics: '宇宙物理学をレベル1まで研究', researchAstrophysics: '宇宙物理学をレベル1まで研究',
researchAstrophysicsHigher: '宇宙物理学をレベル3まで研究',
buildColonyShip: 'コロニーシップを建造', buildColonyShip: 'コロニーシップを建造',
colonizeNewPlanet: '新惑星を植民地化', colonizeNewPlanet: '新惑星を植民地化',
completeExpedition: '遠征ミッションを完了', colonizeMultiple: '5つの惑星を植民地化',
completeExpedition: '3回の遠征ミッションを完了',
expeditionDeepSpace: '2回の深宇宙遠征を完了',
expeditionUncharted: '1回の未知領域探索',
expeditionDangerous: '3回の危険な星雲遠征を完了',
discoverRuins: '古代遺跡を発見', discoverRuins: '古代遺跡を発見',
researchComputer: 'コンピュータ技術をレベル4まで研究', researchComputer: 'コンピュータ技術をレベル4まで研究',
researchImpulse: 'インパルスドライブをレベル3まで研究',
researchLaser: 'レーザー技術をレベル5まで研究',
researchIntergalactic: 'コンピュータ技術をレベル10まで研究',
researchGraviton: 'グラビトン技術をレベル1まで研究',
improveRelation: 'NPCとの関係を改善', improveRelation: 'NPCとの関係を改善',
reachFriendly: 'NPCと友好ステータスに到達', reachFriendly: 'NPCと友好ステータスに到達',
spyHostileNPC: '敵対NPCをスパイ', reachFriendlyRelation: '任意のNPCと友好ステータスに到達',
sendMultipleGifts: 'NPCに3回贈り物を送る',
spyHostileNPC: '2つの敵対NPCをスパイ',
formAlliance: '友好NPCと同盟を結ぶ', formAlliance: '友好NPCと同盟を結ぶ',
buildDefenses: '防衛施設を建設', buildDefenses: '防衛施設を建設',
buildMissileSilo: 'ミサイルサイロをレベル2に建設',
buildCruisers: 'クルーザー10隻を建造',
winDefenseBattle: '防衛戦に勝利', winDefenseBattle: '防衛戦に勝利',
defendAgainstAttack: '1回の攻撃を防御成功',
spyEnemyPlanet: '敵惑星をスパイ', spyEnemyPlanet: '敵惑星をスパイ',
spyEnemyPlanets: '5つの敵惑星をスパイ',
winAttackBattles: '3回の攻撃戦に勝利',
attackEnemy: '敵を攻撃', attackEnemy: '敵を攻撃',
recycleDebris: 'デブリをリサイクル', recycleDebris: '5回デブリをリサイクル',
buildBattleships: 'バトルシップ10隻を建造', buildBattleships: 'バトルシップ20隻を建造',
exploreDeepRuins: '深部遺跡を探索', exploreDeepRuins: '深部遺跡を探索',
researchHyperspace: 'ハイパースペース技術を研究', researchHyperspace: 'ハイパースペースドライブをレベル3まで研究',
defeatBoss: '謎の敵を撃破', defeatBoss: '古代の守護者を撃破',
colonizeSpecial: '特別な場所を植民地化', colonizeSpecial: '特別な場所を植民地化',
accumulateWealth: '総資源100万を蓄積',
continueDevelopment: '発展を継続' continueDevelopment: '発展を継続'
}, },
dialogues: { dialogues: {

View File

@@ -388,6 +388,7 @@ export default {
title: '건물', title: '건물',
spaceUsage: '공간 사용', spaceUsage: '공간 사용',
upgradeCost: '업그레이드 비용', upgradeCost: '업그레이드 비용',
build: '건설',
upgrade: '업그레이드', upgrade: '업그레이드',
maxLevelReached: '최대 레벨 도달', maxLevelReached: '최대 레벨 도달',
requirementsNotMet: '요구 사항 미충족', requirementsNotMet: '요구 사항 미충족',
@@ -408,9 +409,19 @@ export default {
researchFailedMessage: '자원이 충분한지, 전제 조건이 충족되었는지, 또는 다른 연구 작업이 있는지 확인하세요.' researchFailedMessage: '자원이 충분한지, 전제 조건이 충족되었는지, 또는 다른 연구 작업이 있는지 확인하세요.'
}, },
shipyard: { shipyard: {
attack: '공격력',
shield: '실드',
armor: '장갑',
speed: '속도', speed: '속도',
cargoCapacity: '화물 용량', cargoCapacity: '화물 용량',
fuelConsumption: '연료 소비' fuelConsumption: '연료 소비',
buildCost: '건조 비용',
buildTime: '건조 시간',
perUnit: '단위당',
batchCalculator: '일괄 계산기',
quantity: '수량',
totalCost: '총 비용',
totalTime: '총 시간'
}, },
shipyardView: { shipyardView: {
title: '조선소', title: '조선소',
@@ -521,6 +532,8 @@ export default {
}, },
recycle: '회수', recycle: '회수',
destroy: '행성 파괴', destroy: '행성 파괴',
harvestDarkMatter: '암흑 물질 수확',
station: '주둔',
transportResources: '자원 수송', transportResources: '자원 수송',
totalCargoCapacity: '총 적재량', totalCargoCapacity: '총 적재량',
used: '사용됨', used: '사용됨',
@@ -1784,6 +1797,27 @@ export default {
reputationBonusDesc: '동맹 {npcName}이(가) {targetNpc}에게 당신을 좋게 말하고 있습니다' reputationBonusDesc: '동맹 {npcName}이(가) {targetNpc}에게 당신을 좋게 말하고 있습니다'
} }
}, },
webdav: {
connectionSuccess: 'WebDAV 연결 성공',
connectionSuccessDirectoryCreated: 'WebDAV 연결 성공, 저장 디렉토리 생성됨',
authFailed: '인증 실패, 사용자 이름과 비밀번호를 확인하세요',
directoryNotExist: '디렉토리가 존재하지 않으며 생성할 수 없습니다',
networkError: '네트워크 오류, 서버 주소와 네트워크를 확인하세요',
unknownError: '알 수 없는 오류',
uploadSuccess: '저장 파일 업로드 성공',
uploadFailed: '업로드 실패',
downloadSuccess: '저장 파일 다운로드 성공',
downloadFailed: '다운로드 실패',
noSaveFiles: '서버에 저장 파일이 없습니다',
fileListSuccess: '저장 파일 목록 가져오기 성공',
fileListFailed: '저장 파일 목록 가져오기 실패',
deleteSuccess: '저장 파일 삭제 성공',
deleteFailed: '삭제 실패',
serverError: '서버 오류',
notConfigured: 'WebDAV가 구성되지 않았습니다',
invalidUrl: '잘못된 WebDAV URL',
timeout: '연결 시간 초과'
},
campaign: { campaign: {
name: '캠페인', name: '캠페인',
description: '신비로운 은하를 탐험하고 고대의 비밀을 밝혀내세요', description: '신비로운 은하를 탐험하고 고대의 비밀을 밝혀내세요',
@@ -1810,6 +1844,8 @@ export default {
branchUnlocked: '새로운 스토리 분기가 해금되었습니다!' branchUnlocked: '새로운 스토리 분기가 해금되었습니다!'
}, },
dialogue: { dialogue: {
title: '스토리 대화',
description: '캠페인 스토리 대화 내용',
skip: '건너뛰기', skip: '건너뛰기',
continue: '계속', continue: '계속',
finish: '완료', finish: '완료',
@@ -1922,25 +1958,42 @@ export default {
spyAnyNPC: 'NPC 행성 정찰', spyAnyNPC: 'NPC 행성 정찰',
sendGiftToNPC: 'NPC에게 선물 보내기', sendGiftToNPC: 'NPC에게 선물 보내기',
researchAstrophysics: '천체물리학을 레벨 1까지 연구', researchAstrophysics: '천체물리학을 레벨 1까지 연구',
researchAstrophysicsHigher: '천체물리학을 레벨 3까지 연구',
buildColonyShip: '식민선 건조', buildColonyShip: '식민선 건조',
colonizeNewPlanet: '새 행성 식민지화', colonizeNewPlanet: '새 행성 식민지화',
completeExpedition: '탐험 임무 완료', colonizeMultiple: '5개 행성 식민지화',
completeExpedition: '3회 탐험 임무 완료',
expeditionDeepSpace: '2회 심우주 탐험 완료',
expeditionUncharted: '1회 미지 영역 탐험',
expeditionDangerous: '3회 위험 성운 탐험 완료',
discoverRuins: '고대 유적 발견', discoverRuins: '고대 유적 발견',
researchComputer: '컴퓨터 기술을 레벨 4까지 연구', researchComputer: '컴퓨터 기술을 레벨 4까지 연구',
researchImpulse: '임펄스 드라이브를 레벨 3까지 연구',
researchLaser: '레이저 기술을 레벨 5까지 연구',
researchIntergalactic: '컴퓨터 기술을 레벨 10까지 연구',
researchGraviton: '그래비톤 기술을 레벨 1까지 연구',
improveRelation: 'NPC와의 관계 개선', improveRelation: 'NPC와의 관계 개선',
reachFriendly: 'NPC와 우호 상태 도달', reachFriendly: 'NPC와 우호 상태 도달',
spyHostileNPC: '적대 NPC 정찰', reachFriendlyRelation: '임의의 NPC와 우호 상태 도달',
sendMultipleGifts: 'NPC에게 3회 선물 보내기',
spyHostileNPC: '적대 NPC 2곳 정찰',
formAlliance: '우호 NPC와 동맹 체결', formAlliance: '우호 NPC와 동맹 체결',
buildDefenses: '방어 시설 건설', buildDefenses: '방어 시설 건설',
buildMissileSilo: '미사일 사일로를 레벨 2로 건설',
buildCruisers: '순양함 10대 건조',
winDefenseBattle: '방어전 승리', winDefenseBattle: '방어전 승리',
defendAgainstAttack: '1회 공격 방어 성공',
spyEnemyPlanet: '적 행성 정찰', spyEnemyPlanet: '적 행성 정찰',
spyEnemyPlanets: '적 행성 5곳 정찰',
winAttackBattles: '3회 공격전 승리',
attackEnemy: '적 공격', attackEnemy: '적 공격',
recycleDebris: '잔해 재활용', recycleDebris: '5회 잔해 재활용',
buildBattleships: '전함 10대 건조', buildBattleships: '전함 20대 건조',
exploreDeepRuins: '깊은 유적 탐험', exploreDeepRuins: '깊은 유적 탐험',
researchHyperspace: '하이퍼스페이스 기술 연구', researchHyperspace: '하이퍼스페이스 드라이브를 레벨 3까지 연구',
defeatBoss: '신비한 적 격파', defeatBoss: '고대 수호자 격파',
colonizeSpecial: '특별한 장소 식민지화', colonizeSpecial: '특별한 장소 식민지화',
accumulateWealth: '총 자원 100만 축적',
continueDevelopment: '개발 계속' continueDevelopment: '개발 계속'
}, },
dialogues: { dialogues: {

View File

@@ -161,6 +161,7 @@ export default {
totalCost: 'Общая стоимость', totalCost: 'Общая стоимость',
totalPoints: 'Общие очки', totalPoints: 'Общие очки',
levelRange: 'Диапазон уровней', levelRange: 'Диапазон уровней',
researchSpeedBonus: 'Бонус скорости исследований',
capacity: 'Capacity/Effect', capacity: 'Capacity/Effect',
storageCapacity: 'Capacity', storageCapacity: 'Capacity',
energyProduction: 'Energy Production', energyProduction: 'Energy Production',
@@ -269,6 +270,9 @@ export default {
}, },
research: { research: {
researchTime: 'Время исследования', researchTime: 'Время исследования',
levelRange: 'Диапазон уровней',
totalCost: 'Общая стоимость',
totalPoints: 'Всего очков',
capacity: 'Capacity/Effect', capacity: 'Capacity/Effect',
storageCapacity: 'Capacity', storageCapacity: 'Capacity',
energyProduction: 'Energy Production', energyProduction: 'Energy Production',
@@ -402,6 +406,7 @@ export default {
spaceUsage: 'Использование полей', spaceUsage: 'Использование полей',
level: 'Уровень', level: 'Уровень',
upgradeCost: 'Стоимость улучшения', upgradeCost: 'Стоимость улучшения',
build: 'Построить',
upgrade: 'Улучшить', upgrade: 'Улучшить',
maxLevelReached: 'Достигнут максимальный уровень', maxLevelReached: 'Достигнут максимальный уровень',
requirementsNotMet: 'Требования не выполнены', requirementsNotMet: 'Требования не выполнены',
@@ -423,9 +428,19 @@ export default {
'Пожалуйста, проверьте, достаточно ли у вас ресурсов, выполнены ли предварительные условия или нет других исследовательских задач.' 'Пожалуйста, проверьте, достаточно ли у вас ресурсов, выполнены ли предварительные условия или нет других исследовательских задач.'
}, },
shipyard: { shipyard: {
attack: 'Атака',
shield: 'Щит',
armor: 'Броня',
speed: 'Скорость', speed: 'Скорость',
cargoCapacity: 'Грузоподъёмность', cargoCapacity: 'Грузоподъёмность',
fuelConsumption: 'Расход топлива' fuelConsumption: 'Расход топлива',
buildCost: 'Стоимость постройки',
buildTime: 'Время постройки',
perUnit: 'За единицу',
batchCalculator: 'Калькулятор партии',
quantity: 'Количество',
totalCost: 'Общая стоимость',
totalTime: 'Общее время'
}, },
shipyardView: { shipyardView: {
title: 'Верфь', title: 'Верфь',
@@ -607,7 +622,9 @@ export default {
jumpGateSuccessMessage: 'Флот мгновенно переброшен к {target}', jumpGateSuccessMessage: 'Флот мгновенно переброшен к {target}',
jumpGateFailed: 'Переброска через ворота не удалась', jumpGateFailed: 'Переброска через ворота не удалась',
jumpGateFailedMessage: 'Проверьте состояние ворот и конфигурацию флота', jumpGateFailedMessage: 'Проверьте состояние ворот и конфигурацию флота',
destroy: 'Уничтожение' destroy: 'Уничтожение',
harvestDarkMatter: 'Сбор тёмной материи',
station: 'Расположение'
}, },
officersView: { officersView: {
title: 'Офицеры', title: 'Офицеры',
@@ -1836,6 +1853,27 @@ export default {
reputationBonusDesc: 'Ваш союзник {npcName} хорошо отзывается о вас {targetNpc}' reputationBonusDesc: 'Ваш союзник {npcName} хорошо отзывается о вас {targetNpc}'
} }
}, },
webdav: {
connectionSuccess: 'Подключение WebDAV успешно',
connectionSuccessDirectoryCreated: 'Подключение WebDAV успешно, каталог сохранений создан',
authFailed: 'Ошибка аутентификации, проверьте имя пользователя и пароль',
directoryNotExist: 'Каталог не существует и не может быть создан',
networkError: 'Ошибка сети, проверьте адрес сервера и сеть',
unknownError: 'Неизвестная ошибка',
uploadSuccess: 'Сохранение успешно загружено',
uploadFailed: 'Ошибка загрузки',
downloadSuccess: 'Сохранение успешно скачано',
downloadFailed: 'Ошибка скачивания',
noSaveFiles: 'На сервере нет сохранений',
fileListSuccess: 'Список сохранений получен',
fileListFailed: 'Ошибка получения списка сохранений',
deleteSuccess: 'Сохранение успешно удалено',
deleteFailed: 'Ошибка удаления',
serverError: 'Ошибка сервера',
notConfigured: 'WebDAV не настроен',
invalidUrl: 'Недействительный URL WebDAV',
timeout: 'Превышено время ожидания'
},
campaign: { campaign: {
name: 'Кампания', name: 'Кампания',
description: 'Исследуйте загадочную галактику и раскройте древние тайны', description: 'Исследуйте загадочную галактику и раскройте древние тайны',
@@ -1863,6 +1901,8 @@ export default {
branchUnlocked: 'Новая сюжетная ветка разблокирована!' branchUnlocked: 'Новая сюжетная ветка разблокирована!'
}, },
dialogue: { dialogue: {
title: 'Сюжетный диалог',
description: 'Содержание сюжетного диалога кампании',
skip: 'Пропустить', skip: 'Пропустить',
continue: 'Продолжить', continue: 'Продолжить',
finish: 'Завершить', finish: 'Завершить',
@@ -1975,25 +2015,42 @@ export default {
spyAnyNPC: 'Разведать планету NPC', spyAnyNPC: 'Разведать планету NPC',
sendGiftToNPC: 'Отправить подарок NPC', sendGiftToNPC: 'Отправить подарок NPC',
researchAstrophysics: 'Исследовать Астрофизику до уровня 1', researchAstrophysics: 'Исследовать Астрофизику до уровня 1',
researchAstrophysicsHigher: 'Исследовать Астрофизику до уровня 3',
buildColonyShip: 'Построить Колониальный корабль', buildColonyShip: 'Построить Колониальный корабль',
colonizeNewPlanet: 'Колонизировать новую планету', colonizeNewPlanet: 'Колонизировать новую планету',
completeExpedition: 'Завершить экспедиционную миссию', colonizeMultiple: 'Колонизировать 5 планет',
completeExpedition: 'Завершить 3 экспедиционные миссии',
expeditionDeepSpace: 'Завершить 2 экспедиции в глубокий космос',
expeditionUncharted: 'Исследовать 1 неизвестный регион',
expeditionDangerous: 'Завершить 3 экспедиции в опасные туманности',
discoverRuins: 'Обнаружить древние руины', discoverRuins: 'Обнаружить древние руины',
researchComputer: 'Исследовать Компьютерную технологию до уровня 4', researchComputer: 'Исследовать Компьютерную технологию до уровня 4',
researchImpulse: 'Исследовать Импульсный двигатель до уровня 3',
researchLaser: 'Исследовать Лазерную технологию до уровня 5',
researchIntergalactic: 'Исследовать Компьютерную технологию до уровня 10',
researchGraviton: 'Исследовать Гравитонную технологию до уровня 1',
improveRelation: 'Улучшить отношения с NPC', improveRelation: 'Улучшить отношения с NPC',
reachFriendly: 'Достичь дружеского статуса с NPC', reachFriendly: 'Достичь дружеского статуса с NPC',
spyHostileNPC: 'Разведать враждебного NPC', reachFriendlyRelation: 'Достичь дружеского статуса с любым NPC',
sendMultipleGifts: 'Отправить 3 подарка NPC',
spyHostileNPC: 'Разведать 2 враждебных NPC',
formAlliance: 'Заключить альянс с дружественным NPC', formAlliance: 'Заключить альянс с дружественным NPC',
buildDefenses: 'Построить оборонительные сооружения', buildDefenses: 'Построить оборонительные сооружения',
buildMissileSilo: 'Построить Ракетную шахту до уровня 2',
buildCruisers: 'Построить 10 Крейсеров',
winDefenseBattle: 'Выиграть оборонительное сражение', winDefenseBattle: 'Выиграть оборонительное сражение',
defendAgainstAttack: 'Успешно отразить 1 атаку',
spyEnemyPlanet: 'Разведать вражескую планету', spyEnemyPlanet: 'Разведать вражескую планету',
spyEnemyPlanets: 'Разведать 5 вражеских планет',
winAttackBattles: 'Выиграть 3 атакующих сражения',
attackEnemy: 'Атаковать врага', attackEnemy: 'Атаковать врага',
recycleDebris: 'Переработать обломки', recycleDebris: 'Переработать обломки 5 раз',
buildBattleships: 'Построить 10 Линкоров', buildBattleships: 'Построить 20 Линкоров',
exploreDeepRuins: 'Исследовать глубокие руины', exploreDeepRuins: 'Исследовать глубокие руины',
researchHyperspace: 'Исследовать Гиперпространственную технологию', researchHyperspace: 'Исследовать Гиперпространственный двигатель до уровня 3',
defeatBoss: 'Победить загадочного врага', defeatBoss: 'Победить Древнего Стража',
colonizeSpecial: 'Колонизировать особое место', colonizeSpecial: 'Колонизировать особое место',
accumulateWealth: 'Накопить 1 миллион ресурсов',
continueDevelopment: 'Продолжить развитие' continueDevelopment: 'Продолжить развитие'
}, },
dialogues: { dialogues: {

View File

@@ -32,7 +32,8 @@ export default {
exitConfirmTitle: '退出游戏', exitConfirmTitle: '退出游戏',
exitConfirmMessage: '确定要退出游戏吗?游戏进度会自动保存。', exitConfirmMessage: '确定要退出游戏吗?游戏进度会自动保存。',
points: '积分', points: '积分',
retry: '重试' retry: '重试',
playerName: '玩家名称'
}, },
errors: { errors: {
requirementsNotMet: '不满足前置条件', requirementsNotMet: '不满足前置条件',
@@ -264,6 +265,9 @@ export default {
}, },
research: { research: {
researchTime: '研究时间', researchTime: '研究时间',
levelRange: '等级范围',
totalCost: '总成本',
totalPoints: '总积分',
attackBonus: '攻击加成', attackBonus: '攻击加成',
shieldBonus: '护盾加成', shieldBonus: '护盾加成',
@@ -384,6 +388,7 @@ export default {
title: '建筑', title: '建筑',
spaceUsage: '占用空间', spaceUsage: '占用空间',
upgradeCost: '升级消耗', upgradeCost: '升级消耗',
build: '建造',
upgrade: '升级', upgrade: '升级',
maxLevelReached: '等级已满', maxLevelReached: '等级已满',
requirementsNotMet: '条件不足', requirementsNotMet: '条件不足',
@@ -404,9 +409,19 @@ export default {
researchFailedMessage: '请检查资源是否足够、前置条件是否满足,或是否有其他研究任务。' researchFailedMessage: '请检查资源是否足够、前置条件是否满足,或是否有其他研究任务。'
}, },
shipyard: { shipyard: {
attack: '攻击力',
shield: '护盾',
armor: '装甲',
speed: '速度', speed: '速度',
cargoCapacity: '载货量', cargoCapacity: '载货量',
fuelConsumption: '燃料消耗' fuelConsumption: '燃料消耗',
buildCost: '建造成本',
buildTime: '建造时间',
perUnit: '单位',
batchCalculator: '批量计算器',
quantity: '数量',
totalCost: '总成本',
totalTime: '总时间'
}, },
shipyardView: { shipyardView: {
title: '船坞', title: '船坞',
@@ -511,6 +526,8 @@ export default {
}, },
recycle: '回收', recycle: '回收',
destroy: '行星毁灭', destroy: '行星毁灭',
harvestDarkMatter: '暗物质采集',
station: '驻守协防',
transportResources: '运输资源', transportResources: '运输资源',
totalCargoCapacity: '总载货量', totalCargoCapacity: '总载货量',
used: '已用', used: '已用',
@@ -1782,6 +1799,8 @@ export default {
branchUnlocked: '新的故事分支已解锁!' branchUnlocked: '新的故事分支已解锁!'
}, },
dialogue: { dialogue: {
title: '剧情对话',
description: '战役剧情对话内容',
skip: '跳过', skip: '跳过',
continue: '继续', continue: '继续',
finish: '完成', finish: '完成',
@@ -1966,25 +1985,42 @@ export default {
spyAnyNPC: '侦查任意NPC星球', spyAnyNPC: '侦查任意NPC星球',
sendGiftToNPC: '向任意NPC送礼', sendGiftToNPC: '向任意NPC送礼',
researchAstrophysics: '研究天体物理学到1级', researchAstrophysics: '研究天体物理学到1级',
researchAstrophysicsHigher: '研究天体物理学到3级',
buildColonyShip: '建造殖民船', buildColonyShip: '建造殖民船',
colonizeNewPlanet: '殖民新星球', colonizeNewPlanet: '殖民新星球',
completeExpedition: '完成远征任务', colonizeMultiple: '殖民5个星球',
completeExpedition: '完成3次远征任务',
expeditionDeepSpace: '完成2次深空远征',
expeditionUncharted: '探索1次未知区域',
expeditionDangerous: '完成3次危险星云远征',
discoverRuins: '发现古代遗迹', discoverRuins: '发现古代遗迹',
researchComputer: '研究电脑技术到4级', researchComputer: '研究电脑技术到4级',
researchImpulse: '研究脉冲驱动到3级',
researchLaser: '研究激光技术到5级',
researchIntergalactic: '研究电脑技术到10级',
researchGraviton: '研究引力子技术到1级',
improveRelation: '提升与NPC的关系', improveRelation: '提升与NPC的关系',
reachFriendly: '与NPC达到友好关系', reachFriendly: '与NPC达到友好关系',
spyHostileNPC: '侦查敌对NPC', reachFriendlyRelation: '与任意NPC达到友好关系',
sendMultipleGifts: '向NPC发送3次礼物',
spyHostileNPC: '侦查2个敌对NPC',
formAlliance: '与友好NPC结盟', formAlliance: '与友好NPC结盟',
buildDefenses: '建造防御设施', buildDefenses: '建造防御设施',
buildMissileSilo: '建造导弹发射井到2级',
buildCruisers: '建造10艘巡洋舰',
winDefenseBattle: '赢得防御战斗', winDefenseBattle: '赢得防御战斗',
defendAgainstAttack: '成功防御1次攻击',
spyEnemyPlanet: '侦查敌方星球', spyEnemyPlanet: '侦查敌方星球',
spyEnemyPlanets: '侦查5个敌方星球',
winAttackBattles: '赢得3次进攻战斗',
attackEnemy: '攻击敌方', attackEnemy: '攻击敌方',
recycleDebris: '回收残骸', recycleDebris: '回收5次残骸',
buildBattleships: '建造10艘战列舰', buildBattleships: '建造20艘战列舰',
exploreDeepRuins: '探索遗迹深处', exploreDeepRuins: '探索遗迹深处',
researchHyperspace: '研究超空间技术', researchHyperspace: '研究超空间驱动到3级',
defeatBoss: '击败神秘敌人', defeatBoss: '击败古代守护者',
colonizeSpecial: '殖民特殊位置', colonizeSpecial: '殖民特殊位置',
accumulateWealth: '积累总资源达到100万',
continueDevelopment: '继续发展' continueDevelopment: '继续发展'
}, },
dialogues: { dialogues: {
@@ -2075,5 +2111,31 @@ export default {
epilogue_1: '银河系广阔无垠,还有无数秘密等待你去发现...' epilogue_1: '银河系广阔无垠,还有无数秘密等待你去发现...'
} }
} }
},
webdav: {
// 连接相关
connectionSuccess: 'WebDAV 连接成功',
connectionSuccessDirectoryCreated: 'WebDAV 连接成功,已创建存档目录',
authFailed: '认证失败,请检查用户名和密码',
directoryNotExist: '目录不存在且无法创建',
connectionFailedHttp: '连接失败: HTTP {status}',
networkError: '网络错误,可能是 CORS 限制。建议使用支持 CORS 的 WebDAV 服务或通过代理访问。',
connectionError: '连接错误: {error}',
// 上传相关
uploadSuccess: '上传成功',
noWritePermission: '没有写入权限',
insufficientStorage: '存储空间不足',
uploadFailedHttp: '上传失败: HTTP {status}',
uploadError: '上传错误: {error}',
// 下载相关
fileNotExist: '文件不存在',
downloadFailedHttp: '下载失败: HTTP {status}',
downloadError: '下载错误: {error}',
// 列表相关
listFailedHttp: '获取文件列表失败: HTTP {status}',
listError: '获取文件列表错误: {error}',
// 删除相关
deleteFailedHttp: '删除失败: HTTP {status}',
deleteError: '删除错误: {error}'
} }
} }

View File

@@ -270,6 +270,9 @@ export default {
}, },
research: { research: {
researchTime: '研究時間', researchTime: '研究時間',
levelRange: '等級範圍',
totalCost: '總成本',
totalPoints: '總積分',
capacity: '容量/效果', capacity: '容量/效果',
attackBonus: '攻擊加成', attackBonus: '攻擊加成',
shieldBonus: '護盾加成', shieldBonus: '護盾加成',
@@ -414,11 +417,20 @@ export default {
researchFailedMessage: '請檢查資源是否足夠、前置條件是否滿足,或是否有其他研究任務。' researchFailedMessage: '請檢查資源是否足夠、前置條件是否滿足,或是否有其他研究任務。'
}, },
shipyard: { shipyard: {
missileAttack: '導彈攻擊', attack: '攻擊',
shield: '護盾',
armor: '裝甲', armor: '裝甲',
missileAttack: '導彈攻擊',
speed: '速度', speed: '速度',
cargoCapacity: '載貨量', cargoCapacity: '載貨量',
fuelConsumption: '燃料消耗', fuelConsumption: '燃料消耗',
buildCost: '建造成本',
buildTime: '建造時間',
perUnit: '單位',
batchCalculator: '批量計算器',
quantity: '數量',
totalCost: '總成本',
totalTime: '總時間',
build: '建造' build: '建造'
}, },
shipyardView: { shipyardView: {
@@ -530,6 +542,8 @@ export default {
}, },
recycle: '回收', recycle: '回收',
destroy: '行星毀滅', destroy: '行星毀滅',
harvestDarkMatter: '暗物質採集',
station: '駐守協防',
transportResources: '運輸資源', transportResources: '運輸資源',
totalCargoCapacity: '總載貨量', totalCargoCapacity: '總載貨量',
used: '已用', used: '已用',
@@ -1933,6 +1947,27 @@ export default {
reputationBonusDesc: '你的盟友{npcName}向{targetNpc}說了你的好話' reputationBonusDesc: '你的盟友{npcName}向{targetNpc}說了你的好話'
} }
}, },
webdav: {
connectionSuccess: 'WebDAV 連線成功',
connectionSuccessDirectoryCreated: 'WebDAV 連線成功,已建立存檔目錄',
authFailed: '認證失敗,請檢查使用者名稱和密碼',
directoryNotExist: '目錄不存在且無法建立',
networkError: '網路錯誤,請檢查伺服器位址和網路連線',
unknownError: '未知錯誤',
uploadSuccess: '存檔上傳成功',
uploadFailed: '上傳失敗',
downloadSuccess: '存檔下載成功',
downloadFailed: '下載失敗',
noSaveFiles: '伺服器上沒有存檔',
fileListSuccess: '取得存檔列表成功',
fileListFailed: '取得存檔列表失敗',
deleteSuccess: '存檔刪除成功',
deleteFailed: '刪除失敗',
serverError: '伺服器錯誤',
notConfigured: 'WebDAV 未設定',
invalidUrl: '無效的 WebDAV URL',
timeout: '連線逾時'
},
campaign: { campaign: {
name: '戰役', name: '戰役',
description: '探索神秘的銀河系,揭開古代文明的秘密', description: '探索神秘的銀河系,揭開古代文明的秘密',
@@ -1960,6 +1995,8 @@ export default {
branchUnlocked: '新的故事分支已解鎖!' branchUnlocked: '新的故事分支已解鎖!'
}, },
dialogue: { dialogue: {
title: '劇情對話',
description: '戰役劇情對話內容',
skip: '跳過', skip: '跳過',
continue: '繼續', continue: '繼續',
finish: '完成', finish: '完成',
@@ -2144,25 +2181,42 @@ export default {
spyAnyNPC: '偵查任意NPC星球', spyAnyNPC: '偵查任意NPC星球',
sendGiftToNPC: '向任意NPC送禮', sendGiftToNPC: '向任意NPC送禮',
researchAstrophysics: '研究天體物理學到1級', researchAstrophysics: '研究天體物理學到1級',
researchAstrophysicsHigher: '研究天體物理學到3級',
buildColonyShip: '建造殖民船', buildColonyShip: '建造殖民船',
colonizeNewPlanet: '殖民新星球', colonizeNewPlanet: '殖民新星球',
completeExpedition: '完成遠征任務', colonizeMultiple: '殖民5個星球',
completeExpedition: '完成3次遠征任務',
expeditionDeepSpace: '完成2次深空遠征',
expeditionUncharted: '探索1次未知區域',
expeditionDangerous: '完成3次危險星雲遠征',
discoverRuins: '發現古代遺跡', discoverRuins: '發現古代遺跡',
researchComputer: '研究電腦技術到4級', researchComputer: '研究電腦技術到4級',
researchImpulse: '研究脈衝驅動到3級',
researchLaser: '研究雷射技術到5級',
researchIntergalactic: '研究電腦技術到10級',
researchGraviton: '研究引力子技術到1級',
improveRelation: '提升與NPC的關係', improveRelation: '提升與NPC的關係',
reachFriendly: '與NPC達到友好關係', reachFriendly: '與NPC達到友好關係',
spyHostileNPC: '偵查敵對NPC', reachFriendlyRelation: '與任意NPC達到友好關係',
sendMultipleGifts: '向NPC發送3次禮物',
spyHostileNPC: '偵查2個敵對NPC',
formAlliance: '與友好NPC結盟', formAlliance: '與友好NPC結盟',
buildDefenses: '建造防禦設施', buildDefenses: '建造防禦設施',
buildMissileSilo: '建造飛彈發射井到2級',
buildCruisers: '建造10艘巡洋艦',
winDefenseBattle: '贏得防禦戰鬥', winDefenseBattle: '贏得防禦戰鬥',
defendAgainstAttack: '成功防禦1次攻擊',
spyEnemyPlanet: '偵查敵方星球', spyEnemyPlanet: '偵查敵方星球',
spyEnemyPlanets: '偵查5個敵方星球',
winAttackBattles: '贏得3次進攻戰鬥',
attackEnemy: '攻擊敵方', attackEnemy: '攻擊敵方',
recycleDebris: '回收殘骸', recycleDebris: '回收5次殘骸',
buildBattleships: '建造10艘戰列艦', buildBattleships: '建造20艘戰列艦',
exploreDeepRuins: '探索遺跡深處', exploreDeepRuins: '探索遺跡深處',
researchHyperspace: '研究超空間技術', researchHyperspace: '研究超空間驅動到3級',
defeatBoss: '擊敗神秘敵人', defeatBoss: '擊敗古代守護者',
colonizeSpecial: '殖民特殊位置', colonizeSpecial: '殖民特殊位置',
accumulateWealth: '積累總資源達到100萬',
continueDevelopment: '繼續發展' continueDevelopment: '繼續發展'
}, },
dialogues: { dialogues: {

View File

@@ -1,14 +1,13 @@
/** /**
* WebDAV 同步服务 * WebDAV 同步服务
* 支持将存档上传到 WebDAV 服务器如坚果云、Nextcloud、NAS等 * 支持将存档上传到 WebDAV 服务器如坚果云、Nextcloud、NAS等
* 注意WebDAV 配置存储在 gameStore.webdavConfig 中,与用户数据一起持久化
*/ */
export interface WebDAVConfig { import type { WebDAVConfig } from '@/types/game'
serverUrl: string // WebDAV 服务器地址,如 https://dav.jianguoyun.com/dav/
username: string // 用户名 // 重新导出类型以保持向后兼容
password: string // 密码或应用专用密码 export type { WebDAVConfig }
basePath: string // 存档存放路径,如 /ogame-saves/
}
export interface WebDAVFile { export interface WebDAVFile {
name: string name: string
@@ -18,29 +17,47 @@ export interface WebDAVFile {
isDirectory: boolean isDirectory: boolean
} }
const STORAGE_KEY = 'ogame-webdav-config' // WebDAV 消息 key用于 i18n
export const WebDAVMessageKey = {
// 连接相关
connectionSuccess: 'webdav.connectionSuccess',
connectionSuccessDirectoryCreated: 'webdav.connectionSuccessDirectoryCreated',
authFailed: 'webdav.authFailed',
directoryNotExist: 'webdav.directoryNotExist',
connectionFailedHttp: 'webdav.connectionFailedHttp',
networkError: 'webdav.networkError',
connectionError: 'webdav.connectionError',
// 获取保存的 WebDAV 配置 // 上传相关
export const getWebDAVConfig = (): WebDAVConfig | null => { uploadSuccess: 'webdav.uploadSuccess',
try { noWritePermission: 'webdav.noWritePermission',
const saved = localStorage.getItem(STORAGE_KEY) insufficientStorage: 'webdav.insufficientStorage',
if (saved) { uploadFailedHttp: 'webdav.uploadFailedHttp',
return JSON.parse(saved) uploadError: 'webdav.uploadError',
}
} catch (e) {
console.error('Failed to load WebDAV config:', e)
}
return null
}
// 保存 WebDAV 配置 // 下载相关
export const saveWebDAVConfig = (config: WebDAVConfig): void => { fileNotExist: 'webdav.fileNotExist',
localStorage.setItem(STORAGE_KEY, JSON.stringify(config)) downloadFailedHttp: 'webdav.downloadFailedHttp',
} downloadError: 'webdav.downloadError',
// 清除 WebDAV 配置 // 列表相关
export const clearWebDAVConfig = (): void => { listFailedHttp: 'webdav.listFailedHttp',
localStorage.removeItem(STORAGE_KEY) listError: 'webdav.listError',
// 删除相关
deleteFailedHttp: 'webdav.deleteFailedHttp',
deleteError: 'webdav.deleteError'
} as const
export type WebDAVMessageKeyType = (typeof WebDAVMessageKey)[keyof typeof WebDAVMessageKey]
export interface WebDAVResult {
success: boolean
messageKey: WebDAVMessageKeyType
messageParams?: Record<string, string | number>
fileName?: string
data?: string
files?: WebDAVFile[]
} }
// 构建 Authorization header // 构建 Authorization header
@@ -66,7 +83,7 @@ const normalizePath = (serverUrl: string, basePath: string, fileName?: string):
} }
// 测试 WebDAV 连接 // 测试 WebDAV 连接
export const testWebDAVConnection = async (config: WebDAVConfig): Promise<{ success: boolean; message: string }> => { export const testWebDAVConnection = async (config: WebDAVConfig): Promise<WebDAVResult> => {
try { try {
const url = normalizePath(config.serverUrl, config.basePath) const url = normalizePath(config.serverUrl, config.basePath)
@@ -80,33 +97,30 @@ export const testWebDAVConnection = async (config: WebDAVConfig): Promise<{ succ
}) })
if (response.ok || response.status === 207) { if (response.ok || response.status === 207) {
return { success: true, message: 'WebDAV 连接成功' } return { success: true, messageKey: WebDAVMessageKey.connectionSuccess }
} }
if (response.status === 401) { if (response.status === 401) {
return { success: false, message: '认证失败,请检查用户名和密码' } return { success: false, messageKey: WebDAVMessageKey.authFailed }
} }
if (response.status === 404) { if (response.status === 404) {
// 尝试创建目录 // 尝试创建目录
const createResult = await createDirectory(config, config.basePath) const createResult = await createDirectory(config, config.basePath)
if (createResult) { if (createResult) {
return { success: true, message: 'WebDAV 连接成功,已创建存档目录' } return { success: true, messageKey: WebDAVMessageKey.connectionSuccessDirectoryCreated }
} }
return { success: false, message: '目录不存在且无法创建' } return { success: false, messageKey: WebDAVMessageKey.directoryNotExist }
} }
return { success: false, message: `连接失败: HTTP ${response.status}` } return { success: false, messageKey: WebDAVMessageKey.connectionFailedHttp, messageParams: { status: response.status } }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
// CORS 错误的处理 // CORS 错误的处理
if (message.includes('Failed to fetch') || message.includes('NetworkError')) { if (errorMessage.includes('Failed to fetch') || errorMessage.includes('NetworkError')) {
return { return { success: false, messageKey: WebDAVMessageKey.networkError }
success: false,
message: '网络错误,可能是 CORS 限制。建议使用支持 CORS 的 WebDAV 服务或通过代理访问。'
} }
} return { success: false, messageKey: WebDAVMessageKey.connectionError, messageParams: { error: errorMessage } }
return { success: false, message: `连接错误: ${message}` }
} }
} }
@@ -130,11 +144,7 @@ const createDirectory = async (config: WebDAVConfig, path: string): Promise<bool
} }
// 上传存档到 WebDAV // 上传存档到 WebDAV
export const uploadToWebDAV = async ( export const uploadToWebDAV = async (config: WebDAVConfig, data: string, fileName?: string): Promise<WebDAVResult> => {
config: WebDAVConfig,
data: string,
fileName?: string
): Promise<{ success: boolean; message: string; fileName?: string }> => {
try { try {
// 生成带时间戳的文件名 // 生成带时间戳的文件名
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19) const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
@@ -151,25 +161,25 @@ export const uploadToWebDAV = async (
}) })
if (response.ok || response.status === 201 || response.status === 204) { if (response.ok || response.status === 201 || response.status === 204) {
return { success: true, message: '上传成功', fileName: actualFileName } return { success: true, messageKey: WebDAVMessageKey.uploadSuccess, fileName: actualFileName }
} }
if (response.status === 401) { if (response.status === 401) {
return { success: false, message: '认证失败' } return { success: false, messageKey: WebDAVMessageKey.authFailed }
} }
if (response.status === 403) { if (response.status === 403) {
return { success: false, message: '没有写入权限' } return { success: false, messageKey: WebDAVMessageKey.noWritePermission }
} }
if (response.status === 507) { if (response.status === 507) {
return { success: false, message: '存储空间不足' } return { success: false, messageKey: WebDAVMessageKey.insufficientStorage }
} }
return { success: false, message: `上传失败: HTTP ${response.status}` } return { success: false, messageKey: WebDAVMessageKey.uploadFailedHttp, messageParams: { status: response.status } }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
return { success: false, message: `上传错误: ${message}` } return { success: false, messageKey: WebDAVMessageKey.uploadError, messageParams: { error: errorMessage } }
} }
} }
@@ -212,7 +222,7 @@ const parsePropfindResponse = (xml: string, _basePath: string): WebDAVFile[] =>
} }
// 列出 WebDAV 目录中的存档文件 // 列出 WebDAV 目录中的存档文件
export const listWebDAVFiles = async (config: WebDAVConfig): Promise<{ success: boolean; files?: WebDAVFile[]; message?: string }> => { export const listWebDAVFiles = async (config: WebDAVConfig): Promise<WebDAVResult> => {
try { try {
const url = normalizePath(config.serverUrl, config.basePath) const url = normalizePath(config.serverUrl, config.basePath)
@@ -227,29 +237,26 @@ export const listWebDAVFiles = async (config: WebDAVConfig): Promise<{ success:
if (!response.ok && response.status !== 207) { if (!response.ok && response.status !== 207) {
if (response.status === 401) { if (response.status === 401) {
return { success: false, message: '认证失败' } return { success: false, messageKey: WebDAVMessageKey.authFailed }
} }
if (response.status === 404) { if (response.status === 404) {
return { success: false, message: '目录不存在' } return { success: false, messageKey: WebDAVMessageKey.directoryNotExist }
} }
return { success: false, message: `获取文件列表失败: HTTP ${response.status}` } return { success: false, messageKey: WebDAVMessageKey.listFailedHttp, messageParams: { status: response.status } }
} }
const xml = await response.text() const xml = await response.text()
const files = parsePropfindResponse(xml, config.basePath) const files = parsePropfindResponse(xml, config.basePath)
return { success: true, files } return { success: true, messageKey: WebDAVMessageKey.connectionSuccess, files }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
return { success: false, message: `获取文件列表错误: ${message}` } return { success: false, messageKey: WebDAVMessageKey.listError, messageParams: { error: errorMessage } }
} }
} }
// 从 WebDAV 下载存档 // 从 WebDAV 下载存档
export const downloadFromWebDAV = async ( export const downloadFromWebDAV = async (config: WebDAVConfig, fileName: string): Promise<WebDAVResult> => {
config: WebDAVConfig,
fileName: string
): Promise<{ success: boolean; data?: string; message?: string }> => {
try { try {
const url = normalizePath(config.serverUrl, config.basePath, fileName) const url = normalizePath(config.serverUrl, config.basePath, fileName)
@@ -262,27 +269,24 @@ export const downloadFromWebDAV = async (
if (!response.ok) { if (!response.ok) {
if (response.status === 401) { if (response.status === 401) {
return { success: false, message: '认证失败' } return { success: false, messageKey: WebDAVMessageKey.authFailed }
} }
if (response.status === 404) { if (response.status === 404) {
return { success: false, message: '文件不存在' } return { success: false, messageKey: WebDAVMessageKey.fileNotExist }
} }
return { success: false, message: `下载失败: HTTP ${response.status}` } return { success: false, messageKey: WebDAVMessageKey.downloadFailedHttp, messageParams: { status: response.status } }
} }
const data = await response.text() const data = await response.text()
return { success: true, data } return { success: true, messageKey: WebDAVMessageKey.connectionSuccess, data }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
return { success: false, message: `下载错误: ${message}` } return { success: false, messageKey: WebDAVMessageKey.downloadError, messageParams: { error: errorMessage } }
} }
} }
// 删除 WebDAV 文件 // 删除 WebDAV 文件
export const deleteFromWebDAV = async ( export const deleteFromWebDAV = async (config: WebDAVConfig, fileName: string): Promise<WebDAVResult> => {
config: WebDAVConfig,
fileName: string
): Promise<{ success: boolean; message?: string }> => {
try { try {
const url = normalizePath(config.serverUrl, config.basePath, fileName) const url = normalizePath(config.serverUrl, config.basePath, fileName)
@@ -294,20 +298,20 @@ export const deleteFromWebDAV = async (
}) })
if (response.ok || response.status === 204) { if (response.ok || response.status === 204) {
return { success: true } return { success: true, messageKey: WebDAVMessageKey.connectionSuccess }
} }
if (response.status === 401) { if (response.status === 401) {
return { success: false, message: '认证失败' } return { success: false, messageKey: WebDAVMessageKey.authFailed }
} }
if (response.status === 404) { if (response.status === 404) {
return { success: true } // 文件不存在也视为删除成功 return { success: true, messageKey: WebDAVMessageKey.connectionSuccess } // 文件不存在也视为删除成功
} }
return { success: false, message: `删除失败: HTTP ${response.status}` } return { success: false, messageKey: WebDAVMessageKey.deleteFailedHttp, messageParams: { status: response.status } }
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
return { success: false, message: `删除错误: ${message}` } return { success: false, messageKey: WebDAVMessageKey.deleteError, messageParams: { error: errorMessage } }
} }
} }

View File

@@ -12,7 +12,8 @@ import type {
IncomingFleetAlert, IncomingFleetAlert,
MissileAttack, MissileAttack,
AchievementStats, AchievementStats,
AchievementProgress AchievementProgress,
WebDAVConfig
} from '@/types/game' } from '@/types/game'
import { TechnologyType, OfficerType } from '@/types/game' import { TechnologyType, OfficerType } from '@/types/game'
import { initializeAchievementStats, initializeAchievements } from '@/logic/achievementLogic' import { initializeAchievementStats, initializeAchievements } from '@/logic/achievementLogic'
@@ -61,7 +62,8 @@ export const useGameStore = defineStore('game', {
research: true, research: true,
unlock: true unlock: true
} }
} },
webdavConfig: null as WebDAVConfig | null
}), }),
actions: { actions: {
async requestBrowserPermission(): Promise<boolean> { async requestBrowserPermission(): Promise<boolean> {

View File

@@ -120,6 +120,15 @@
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
} }
html {
/* 平滑过渡 */
transition: background-color 0.3s ease, color 0.3s ease;
/* 禁用文本大小调整,防止移动端自动放大文本 */
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
text-size-adjust: 100%;
}
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
@@ -128,11 +137,6 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
/* 平滑过渡 */
html {
transition: background-color 0.3s ease, color 0.3s ease;
}
/* 滚动条样式 */ /* 滚动条样式 */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 8px;
@@ -218,3 +222,21 @@ aside nav a:hover button {
background-color: oklch(0.3 0.02 85) !important; background-color: oklch(0.3 0.02 85) !important;
color: oklch(0.95 0.008 85) !important; color: oklch(0.95 0.008 85) !important;
} }
/* 修复 Edge-to-Edge 模式下 Portal 容器的定位问题 */
[data-reka-portal],
[data-radix-portal] {
position: fixed !important;
left: 0 !important;
top: 0 !important;
right: 0 !important;
bottom: 0 !important;
pointer-events: none;
padding: 0 !important;
margin: 0 !important;
}
[data-reka-portal] > *,
[data-radix-portal] > * {
pointer-events: auto;
}

View File

@@ -1226,3 +1226,11 @@ export interface QuestNotification {
rewards?: QuestReward rewards?: QuestReward
read?: boolean read?: boolean
} }
// WebDAV 配置
export interface WebDAVConfig {
serverUrl: string // WebDAV 服务器地址
username: string // 用户名
password: string // 密码或应用专用密码
basePath: string // 存档存放路径
}

View File

@@ -181,133 +181,6 @@
</ScrollableDialogContent> </ScrollableDialogContent>
</Dialog> </Dialog>
<!-- NPC互动面板 - 贸易提议情报联合攻击邀请 -->
<div v-if="hasNpcInteractions" class="space-y-4">
<Collapsible v-model:open="interactionPanelOpen" class="border rounded-lg">
<CollapsibleTrigger class="flex items-center justify-between w-full p-4 hover:bg-accent/50 transition-colors">
<div class="flex items-center gap-2">
<Handshake class="h-5 w-5 text-primary" />
<span class="font-semibold">{{ t('npcBehavior.trade.title') }} & {{ t('npcBehavior.intel.title') }}</span>
<Badge variant="destructive" v-if="totalInteractionCount > 0">{{ totalInteractionCount }}</Badge>
</div>
<ChevronDown class="h-4 w-4 transition-transform" :class="{ 'rotate-180': interactionPanelOpen }" />
</CollapsibleTrigger>
<CollapsibleContent class="px-4 pb-4 space-y-4">
<!-- 贸易提议 -->
<div v-if="activeTradeOffers.length > 0">
<h3 class="text-sm font-semibold mb-2 flex items-center gap-2">
<ArrowLeftRight class="h-4 w-4" />
{{ t('npcBehavior.trade.title') }} ({{ activeTradeOffers.length }})
</h3>
<div class="grid gap-2">
<Card v-for="offer in activeTradeOffers" :key="offer.id" class="p-3">
<div class="flex items-start justify-between gap-4">
<div class="flex-1 space-y-1">
<div class="font-medium">{{ getNpcName(offer.npcId) }}</div>
<div class="text-sm text-muted-foreground">
<span class="text-green-600 dark:text-green-400">{{ t('npcBehavior.trade.offers') }}:</span>
{{ formatResources(offer.offeredResources) }}
</div>
<div class="text-sm text-muted-foreground">
<span class="text-red-600 dark:text-red-400">{{ t('npcBehavior.trade.requests') }}:</span>
{{ formatResources(offer.requestedResources) }}
</div>
<div class="text-xs text-muted-foreground">
{{ t('npcBehavior.trade.expiresIn') }}: {{ formatTimeRemaining(offer.expiresAt) }}
</div>
</div>
<div class="flex gap-2">
<Button size="sm" variant="default" @click="acceptTradeOffer(offer)" :disabled="!canAcceptTrade(offer)">
{{ t('npcBehavior.trade.accept') }}
</Button>
<Button size="sm" variant="outline" @click="declineTradeOffer(offer)">
{{ t('npcBehavior.trade.decline') }}
</Button>
</div>
</div>
</Card>
</div>
</div>
<!-- 情报报告 -->
<div v-if="unreadIntelReports.length > 0">
<h3 class="text-sm font-semibold mb-2 flex items-center gap-2">
<Eye class="h-4 w-4" />
{{ t('npcBehavior.intel.title') }} ({{ unreadIntelReports.length }})
</h3>
<div class="grid gap-2">
<Card v-for="intel in unreadIntelReports" :key="intel.id" class="p-3">
<div class="flex items-start justify-between gap-4">
<div class="flex-1 space-y-1">
<div class="font-medium">{{ t('npcBehavior.intel.from') }}: {{ getNpcName(intel.fromNpcId) }}</div>
<div class="text-sm text-muted-foreground">
{{ t('npcBehavior.intel.target') }}: {{ getNpcName(intel.targetNpcId) }}
</div>
<div class="text-sm">
<Badge variant="outline">{{ t(`npcBehavior.intel.types.${intel.intelType}`) }}</Badge>
</div>
<div v-if="intel.data?.fleet" class="text-sm text-muted-foreground">
{{ t('npcBehavior.intel.fleetInfo') }}: {{ formatFleetInfo(intel.data.fleet) }}
</div>
<div v-if="intel.data?.resources" class="text-sm text-muted-foreground">
{{ t('npcBehavior.intel.resourceInfo') }}: {{ formatResources(intel.data.resources as Resources) }}
</div>
</div>
<Button size="sm" variant="ghost" @click="markIntelAsRead(intel)">
{{ t('npcBehavior.intel.markAsRead') }}
</Button>
</div>
</Card>
</div>
</div>
<!-- 联合攻击邀请 -->
<div v-if="activeJointAttackInvites.length > 0">
<h3 class="text-sm font-semibold mb-2 flex items-center gap-2">
<Swords class="h-4 w-4" />
{{ t('npcBehavior.jointAttack.title') }} ({{ activeJointAttackInvites.length }})
</h3>
<div class="grid gap-2">
<Card v-for="invite in activeJointAttackInvites" :key="invite.id" class="p-3">
<div class="flex items-start justify-between gap-4">
<div class="flex-1 space-y-1">
<div class="font-medium">{{ t('npcBehavior.jointAttack.from') }}: {{ getNpcName(invite.fromNpcId) }}</div>
<div class="text-sm text-muted-foreground">
{{ t('npcBehavior.jointAttack.target') }}: {{ getNpcName(invite.targetNpcId) }}
</div>
<div class="text-sm text-muted-foreground">
{{ t('npcBehavior.jointAttack.targetPlanet') }}: [{{ invite.targetPosition.galaxy }}:{{
invite.targetPosition.system
}}:{{ invite.targetPosition.position }}]
</div>
<div class="text-sm text-muted-foreground">
{{ t('npcBehavior.jointAttack.lootShare') }}: {{ (invite.expectedLootRatio * 100).toFixed(0) }}%
</div>
<div class="text-xs text-muted-foreground">
{{ t('npcBehavior.jointAttack.expiresIn') }}: {{ formatTimeRemaining(invite.expiresAt) }}
</div>
</div>
<div class="flex gap-2">
<Button size="sm" variant="default" @click="acceptJointAttack(invite)">
{{ t('npcBehavior.jointAttack.accept') }}
</Button>
<Button size="sm" variant="outline" @click="declineJointAttack(invite)">
{{ t('npcBehavior.jointAttack.decline') }}
</Button>
</div>
</div>
</Card>
</div>
</div>
<!-- 无互动内容提示 -->
<div v-if="!hasActiveInteractions" class="text-center py-4 text-muted-foreground">
{{ t('npcBehavior.trade.noOffers') }}
</div>
</CollapsibleContent>
</Collapsible>
</div>
<!-- 搜索框 --> <!-- 搜索框 -->
<div class="relative"> <div class="relative">
<Search class="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Search class="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
@@ -503,14 +376,11 @@
import { useGameStore } from '@/stores/gameStore' import { useGameStore } from '@/stores/gameStore'
import { useNPCStore } from '@/stores/npcStore' import { useNPCStore } from '@/stores/npcStore'
import { useI18n } from '@/composables/useI18n' import { useI18n } from '@/composables/useI18n'
import { toast } from 'vue-sonner'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Dialog, DialogDescription, DialogTitle } from '@/components/ui/dialog' import { Dialog, DialogDescription, DialogTitle } from '@/components/ui/dialog'
import ScrollableDialogContent from '@/components/ui/dialog/ScrollableDialogContent.vue' import ScrollableDialogContent from '@/components/ui/dialog/ScrollableDialogContent.vue'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
import { import {
FixedPagination, FixedPagination,
Pagination, Pagination,
@@ -523,7 +393,7 @@
import NpcRelationCard from '@/components/npc/NpcRelationCard.vue' import NpcRelationCard from '@/components/npc/NpcRelationCard.vue'
import NpcRelationRow from '@/components/npc/NpcRelationRow.vue' import NpcRelationRow from '@/components/npc/NpcRelationRow.vue'
import { RelationStatus } from '@/types/game' import { RelationStatus } from '@/types/game'
import type { DiplomaticRelation, TradeOffer, IntelReport, JointAttackInvite, Resources } from '@/types/game' import type { DiplomaticRelation } from '@/types/game'
import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic' import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic'
import { import {
Search, Search,
@@ -533,11 +403,7 @@
Swords, Swords,
Activity, Activity,
LayoutGrid, LayoutGrid,
List, List
Handshake,
ChevronDown,
ArrowLeftRight,
Eye
} from 'lucide-vue-next' } from 'lucide-vue-next'
import { Empty, EmptyContent, EmptyDescription } from '@/components/ui/empty' import { Empty, EmptyContent, EmptyDescription } from '@/components/ui/empty'
@@ -548,9 +414,6 @@
const activeTab = ref('all') const activeTab = ref('all')
// NPC互动面板状态
const interactionPanelOpen = ref(true)
// 视图模式: 'card' | 'list' // 视图模式: 'card' | 'list'
const viewMode = ref<'card' | 'list'>('list') const viewMode = ref<'card' | 'list'>('list')
@@ -864,188 +727,6 @@
currentPage.value[activeTab.value] = val currentPage.value[activeTab.value] = val
} }
}) })
// ========== NPC互动面板相关 ==========
// 获取当前时间戳
const now = computed(() => Date.now())
// 有效的贸易提议(未过期)
const activeTradeOffers = computed(() => {
return (gameStore.player.tradeOffers || []).filter(offer => offer.expiresAt > now.value)
})
// 未读的情报报告
const unreadIntelReports = computed(() => {
return (gameStore.player.intelReports || []).filter(report => !report.read)
})
// 有效的联合攻击邀请(未过期)
const activeJointAttackInvites = computed(() => {
return (gameStore.player.jointAttackInvites || []).filter(invite => invite.expiresAt > now.value)
})
// 是否有NPC互动数据
const hasNpcInteractions = computed(() => {
return (
(gameStore.player.tradeOffers?.length || 0) > 0 ||
(gameStore.player.intelReports?.length || 0) > 0 ||
(gameStore.player.jointAttackInvites?.length || 0) > 0
)
})
// 是否有有效的互动内容
const hasActiveInteractions = computed(() => {
return activeTradeOffers.value.length > 0 || unreadIntelReports.value.length > 0 || activeJointAttackInvites.value.length > 0
})
// 总互动数量(用于显示徽章)
const totalInteractionCount = computed(() => {
return activeTradeOffers.value.length + unreadIntelReports.value.length + activeJointAttackInvites.value.length
})
// 获取NPC名称
const getNpcName = (npcId: string): string => {
const npc = npcStore.npcs.find(n => n.id === npcId)
return npc?.name || npcId
}
// 格式化资源显示
// 格式化资源(兼容新旧格式)
const formatResources = (resources: Resources | { type: string; amount: number }): string => {
// 新格式:{ type: 'metal', amount: 1000 }
if ('type' in resources && 'amount' in resources) {
const typeLabels: Record<string, string> = {
metal: 'M',
crystal: 'C',
deuterium: 'D'
}
return `${Math.floor(resources.amount).toLocaleString()} ${typeLabels[resources.type] || resources.type}`
}
// 旧格式:{ metal: 1000, crystal: 0, deuterium: 0 }
const parts: string[] = []
if ((resources as Resources).metal > 0) parts.push(`${Math.floor((resources as Resources).metal).toLocaleString()} M`)
if ((resources as Resources).crystal > 0) parts.push(`${Math.floor((resources as Resources).crystal).toLocaleString()} C`)
if ((resources as Resources).deuterium > 0) parts.push(`${Math.floor((resources as Resources).deuterium).toLocaleString()} D`)
return parts.join(' / ') || '-'
}
// 格式化舰队信息
const formatFleetInfo = (fleetInfo: Record<string, number>): string => {
const parts: string[] = []
for (const [shipType, count] of Object.entries(fleetInfo)) {
if (count > 0) {
parts.push(`${shipType}: ${count}`)
}
}
return parts.join(', ') || '-'
}
// 格式化剩余时间
const formatTimeRemaining = (expiresAt: number): string => {
const remaining = expiresAt - now.value
if (remaining <= 0) return t('npcBehavior.trade.expired')
const minutes = Math.floor(remaining / 60000)
const hours = Math.floor(minutes / 60)
const mins = minutes % 60
if (hours > 0) {
return `${hours}h ${mins}m`
}
return `${mins}m`
}
// 检查是否可以接受贸易(兼容新格式 { type, amount }
const canAcceptTrade = (offer: TradeOffer): boolean => {
const planet = gameStore.player.planets[0]
if (!planet) return false
// 新格式:{ type: 'metal', amount: 1000 }
const requestedType = offer.requestedResources.type
const requestedAmount = offer.requestedResources.amount
return planet.resources[requestedType] >= requestedAmount
}
// 接受贸易提议
const acceptTradeOffer = (offer: TradeOffer) => {
if (!canAcceptTrade(offer)) {
toast.error(t('npcBehavior.trade.acceptFailed'))
return
}
const planet = gameStore.player.planets[0]
if (!planet) return
// 新格式:{ type: 'metal', amount: 1000 }
const requestedType = offer.requestedResources.type
const requestedAmount = offer.requestedResources.amount
const offeredType = offer.offeredResources.type
const offeredAmount = offer.offeredResources.amount
// 扣除请求的资源
planet.resources[requestedType] -= requestedAmount
// 添加获得的资源
planet.resources[offeredType] += offeredAmount
// 移除贸易提议
const index = gameStore.player.tradeOffers?.indexOf(offer)
if (index !== undefined && index >= 0) {
gameStore.player.tradeOffers?.splice(index, 1)
}
// 提高与该NPC的好感度使用 npcId 而不是 fromNpcId
const npcRelation = npcStore.npcs.find(n => n.id === offer.npcId)?.relations?.[gameStore.player.id]
if (npcRelation) {
npcRelation.reputation += 10
}
toast.success(t('npcBehavior.trade.acceptSuccess'))
}
// 拒绝贸易提议
const declineTradeOffer = (offer: TradeOffer) => {
const index = gameStore.player.tradeOffers?.indexOf(offer)
if (index !== undefined && index >= 0) {
gameStore.player.tradeOffers?.splice(index, 1)
}
toast.info(t('npcBehavior.trade.declined'))
}
// 标记情报为已读
const markIntelAsRead = (intel: IntelReport) => {
intel.read = true
}
// 接受联合攻击邀请
const acceptJointAttack = (invite: JointAttackInvite) => {
// 这里可以添加联合攻击的逻辑
// 目前只是简单地移除邀请并显示提示
const index = gameStore.player.jointAttackInvites?.indexOf(invite)
if (index !== undefined && index >= 0) {
gameStore.player.jointAttackInvites?.splice(index, 1)
}
// 提高与该NPC的好感度使用 npcStore
const npcRelation = npcStore.npcs.find(n => n.id === invite.fromNpcId)?.relations?.[gameStore.player.id]
if (npcRelation) {
npcRelation.reputation += 15
}
toast.success(t('npcBehavior.jointAttack.acceptSuccess'))
}
// 拒绝联合攻击邀请
const declineJointAttack = (invite: JointAttackInvite) => {
const index = gameStore.player.jointAttackInvites?.indexOf(invite)
if (index !== undefined && index >= 0) {
gameStore.player.jointAttackInvites?.splice(index, 1)
}
toast.info(t('npcBehavior.jointAttack.declined'))
}
</script> </script>
<style> <style>

View File

@@ -1117,13 +1117,17 @@
// 获取任务类型名称 // 获取任务类型名称
const getMissionTypeName = (missionType: string): string => { const getMissionTypeName = (missionType: string): string => {
const typeMap: Record<string, string> = { const typeMap: Record<string, string> = {
[MissionType.Attack]: t('fleetView.attack'),
[MissionType.Transport]: t('fleetView.transport'), [MissionType.Transport]: t('fleetView.transport'),
[MissionType.Colonize]: t('fleetView.colonize'), [MissionType.Colonize]: t('fleetView.colonize'),
[MissionType.Spy]: t('fleetView.spy'),
[MissionType.Deploy]: t('fleetView.deploy'), [MissionType.Deploy]: t('fleetView.deploy'),
[MissionType.Expedition]: t('fleetView.expedition'), [MissionType.Expedition]: t('fleetView.expedition'),
[MissionType.Recycle]: t('fleetView.recycle'), [MissionType.Recycle]: t('fleetView.recycle'),
[MissionType.Destroy]: t('fleetView.destroy'), [MissionType.Destroy]: t('fleetView.destroy'),
[MissionType.MissileAttack]: t('galaxyView.missileAttack') [MissionType.MissileAttack]: t('galaxyView.missileAttack'),
[MissionType.HarvestDarkMatter]: t('fleetView.harvestDarkMatter'),
[MissionType.Station]: t('fleetView.station')
} }
return typeMap[missionType] || missionType return typeMap[missionType] || missionType
} }

View File

@@ -334,7 +334,7 @@
<PrivacyDialog v-model:open="showPrivacyDialog" /> <PrivacyDialog v-model:open="showPrivacyDialog" />
<!-- WebDAV 配置对话框 --> <!-- WebDAV 配置对话框 -->
<WebDAVConfigDialog v-model:open="showWebDAVConfig" @saved="onWebDAVConfigSaved" /> <WebDAVConfigDialog v-model:open="showWebDAVConfig" />
<!-- WebDAV 文件列表对话框 --> <!-- WebDAV 文件列表对话框 -->
<WebDAVFileListDialog v-model:open="showWebDAVFiles" :config="webdavConfig" @select="handleWebDAVDownload" /> <WebDAVFileListDialog v-model:open="showWebDAVFiles" :config="webdavConfig" @select="handleWebDAVDownload" />
@@ -389,12 +389,7 @@
import WebDAVConfigDialog from '@/components/settings/WebDAVConfigDialog.vue' import WebDAVConfigDialog from '@/components/settings/WebDAVConfigDialog.vue'
import WebDAVFileListDialog from '@/components/settings/WebDAVFileListDialog.vue' import WebDAVFileListDialog from '@/components/settings/WebDAVFileListDialog.vue'
import { useHints } from '@/composables/useHints' import { useHints } from '@/composables/useHints'
import { import { uploadToWebDAV, downloadFromWebDAV } from '@/services/webdavService'
type WebDAVConfig,
getWebDAVConfig,
uploadToWebDAV,
downloadFromWebDAV
} from '@/services/webdavService'
const { t } = useI18n() const { t } = useI18n()
const { hintsEnabled, setHintsEnabled, resetHints } = useHints() const { hintsEnabled, setHintsEnabled, resetHints } = useHints()
@@ -415,7 +410,7 @@
// WebDAV 相关状态 // WebDAV 相关状态
const showWebDAVConfig = ref(false) const showWebDAVConfig = ref(false)
const showWebDAVFiles = ref(false) const showWebDAVFiles = ref(false)
const webdavConfig = ref<WebDAVConfig | null>(getWebDAVConfig()) const webdavConfig = computed(() => gameStore.webdavConfig)
const isWebDAVUploading = ref(false) const isWebDAVUploading = ref(false)
// 确保通知设置存在 // 确保通知设置存在
@@ -758,11 +753,6 @@
gameStore.player.backgroundEnabled = val gameStore.player.backgroundEnabled = val
} }
// WebDAV 配置保存回调
const onWebDAVConfigSaved = () => {
webdavConfig.value = getWebDAVConfig()
}
// WebDAV 上传 // WebDAV 上传
const handleWebDAVUpload = async () => { const handleWebDAVUpload = async () => {
if (!webdavConfig.value) return if (!webdavConfig.value) return
@@ -792,7 +782,7 @@
if (result.success) { if (result.success) {
toast.success(t('settings.webdav.uploadSuccess')) toast.success(t('settings.webdav.uploadSuccess'))
} else { } else {
toast.error(result.message || t('settings.webdav.uploadFailed')) toast.error(t(result.messageKey) || t('settings.webdav.uploadFailed'))
} }
} catch (error) { } catch (error) {
console.error('WebDAV upload failed:', error) console.error('WebDAV upload failed:', error)
@@ -810,7 +800,7 @@
const result = await downloadFromWebDAV(webdavConfig.value, fileName) const result = await downloadFromWebDAV(webdavConfig.value, fileName)
if (!result.success || !result.data) { if (!result.success || !result.data) {
toast.error(result.message || t('settings.webdav.downloadFailed')) toast.error(t(result.messageKey) || t('settings.webdav.downloadFailed'))
return return
} }

View File

@@ -4,11 +4,11 @@ import tailwindcss from '@tailwindcss/vite'
import path from 'node:path' import path from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { VitePWA } from 'vite-plugin-pwa' import { VitePWA } from 'vite-plugin-pwa'
import autoprefixer from 'autoprefixer'
import pkg from './package.json' import pkg from './package.json'
const __dirname = path.dirname(fileURLToPath(import.meta.url)) const __dirname = path.dirname(fileURLToPath(import.meta.url))
// @ts-expect-error - Vite CSS transformer type
export default defineConfig(async () => { export default defineConfig(async () => {
const plugins = [ const plugins = [
vue(), vue(),
@@ -110,12 +110,19 @@ export default defineConfig(async () => {
plugins, plugins,
resolve: { alias: { '@': path.resolve(__dirname, './src') } }, resolve: { alias: { '@': path.resolve(__dirname, './src') } },
css: { css: {
postcss: { // 使用 lightningcss 处理 CSS自动转换 oklch 等新语法为兼容格式
plugins: [ transformer: 'lightningcss',
autoprefixer({ lightningcss: {
overrideBrowserslist: ['Android >= 4.1', 'iOS >= 7.1', 'Chrome >= 31', 'Firefox >= 31', 'ie >= 8'] // 目标浏览器Android 5+, iOS 10+, Chrome 60+
}) targets: {
] android: 5 << 16, // Android 5.0
chrome: 60 << 16, // Chrome 60
ios_saf: 10 << 16 // iOS Safari 10
},
// 禁用现代 CSS 特性,确保兼容旧版浏览器
drafts: {
customMedia: false
}
} }
}, },
// 优化依赖预构建 // 优化依赖预构建