diff --git a/.gitignore b/.gitignore
index 24e78be..ab6140d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,3 +66,6 @@ backend/gencode
# iOS 证书私钥(绝不入库)
.ios-certs/
+
+# brainstorming visual companion
+.superpowers/
diff --git a/CHANGELOG-client.md b/CHANGELOG-client.md
index 9f76ad4..7f6937a 100644
--- a/CHANGELOG-client.md
+++ b/CHANGELOG-client.md
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [1.0.61] - 2026-06-20
+
+### 改进
+- 全平台应用名统一为「岩美酒库」:macOS 此前窗口标题与应用名显示为 jiu_client,现与各端一致
+- 应用图标更新为最新品牌图标,覆盖 Windows 桌面 / 任务栏 / 标题栏 / 快捷方式及 macOS、iOS、Android、网页各端
+
+### 修复
+- 修复设备管理中 Windows 设备「平台」显示为「未知 桌面端」的问题,更新本版本并重新登录后正确显示为 Windows
+
## [1.0.60] - 2026-06-20
### 新功能
diff --git a/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index 5850b08..316b74c 100644
Binary files a/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index f8b6594..4f3d734 100644
Binary files a/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index c5dfb0a..7e8a021 100644
Binary files a/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 8dc8ba6..a6e72e7 100644
Binary files a/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 0638d8a..9bf88e0 100644
Binary files a/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj
index 6228dd0..901c5fc 100644
--- a/client/ios/Runner.xcodeproj/project.pbxproj
+++ b/client/ios/Runner.xcodeproj/project.pbxproj
@@ -553,7 +553,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -610,7 +610,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
index d36b1fa..d0d98aa 100644
--- a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,122 +1 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "Icon-App-83.5x83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon-App-1024x1024@1x.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
+{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
\ No newline at end of file
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
index 3084564..26c9c26 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
index 05692fc..c18cfd3 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
index 27e351e..b5aed87 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
index bf75174..3d35ace 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
index b2e58e7..41cf23b 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
index d50fd6a..1e2fb31 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
index 7314fb7..5fcc2aa 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
index 27e351e..b5aed87 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
index e1ccbf4..a61a1a3 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
index b5935c3..5be98be 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
new file mode 100644
index 0000000..cf34c15
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
new file mode 100644
index 0000000..0e95107
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
new file mode 100644
index 0000000..2b7e880
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
new file mode 100644
index 0000000..9f55d8f
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
index b5935c3..5be98be 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
index 8d5bed8..42650af 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
new file mode 100644
index 0000000..6f09729
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
new file mode 100644
index 0000000..dbbd774
Binary files /dev/null and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
index 8b89f29..67eb3e2 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
index 2333834..95425ed 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
index a10d86f..9f78c39 100644
Binary files a/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/client/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/client/ios/Runner/Info.plist b/client/ios/Runner/Info.plist
index 07a42b8..934f947 100644
--- a/client/ios/Runner/Info.plist
+++ b/client/ios/Runner/Info.plist
@@ -23,7 +23,7 @@
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- jiu_client
+ 岩美酒库
CFBundlePackageType
APPL
CFBundleShortVersionString
diff --git a/client/lib/core/api/api_client.dart b/client/lib/core/api/api_client.dart
index 2861b30..844ed46 100644
--- a/client/lib/core/api/api_client.dart
+++ b/client/lib/core/api/api_client.dart
@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../auth/auth_state.dart';
import '../config/app_config.dart';
+import '../config/app_constants.dart';
import '../config/license_copy.dart';
import '../errors/error_reporter.dart';
import '../../providers/connectivity_provider.dart';
@@ -15,8 +16,8 @@ final _publicDio = _buildPublicDio();
Dio _buildPublicDio() {
final dio = Dio(BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
- connectTimeout: const Duration(seconds: 8),
- receiveTimeout: const Duration(seconds: 15),
+ connectTimeout: AppConstants.publicConnectTimeout,
+ receiveTimeout: AppConstants.publicReceiveTimeout,
));
dio.interceptors.add(RetryInterceptor(dio));
return dio;
@@ -102,8 +103,8 @@ class ApiClient {
_onAuthFailed = onAuthFailed;
_dio = Dio(BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
- connectTimeout: const Duration(seconds: 8),
- receiveTimeout: const Duration(seconds: 15),
+ connectTimeout: AppConstants.publicConnectTimeout,
+ receiveTimeout: AppConstants.publicReceiveTimeout,
headers: {
if (token != null) 'Authorization': 'Bearer $token',
},
diff --git a/client/lib/core/api/retry_interceptor.dart b/client/lib/core/api/retry_interceptor.dart
index 48d5794..932449d 100644
--- a/client/lib/core/api/retry_interceptor.dart
+++ b/client/lib/core/api/retry_interceptor.dart
@@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
+import '../config/app_constants.dart';
/// 网络层错误自动重试拦截器。
///
@@ -16,11 +17,7 @@ class RetryInterceptor extends Interceptor {
RetryInterceptor(
this.dio, {
- this.delays = const [
- Duration(seconds: 1),
- Duration(seconds: 2),
- Duration(seconds: 4),
- ],
+ this.delays = AppConstants.retryBackoffs,
});
static const _attemptKey = 'retry_attempt';
diff --git a/client/lib/core/auth/auth_state.dart b/client/lib/core/auth/auth_state.dart
index c67c792..7d23bd0 100644
--- a/client/lib/core/auth/auth_state.dart
+++ b/client/lib/core/auth/auth_state.dart
@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../config/app_config.dart';
+import '../config/app_constants.dart';
const _kAccessToken = 'access_token';
const _kRefreshToken = 'refresh_token';
@@ -153,8 +154,8 @@ class AuthNotifier extends StateNotifier {
try {
await Dio(BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
- connectTimeout: const Duration(seconds: 4),
- receiveTimeout: const Duration(seconds: 4),
+ connectTimeout: AppConstants.authProbeTimeout,
+ receiveTimeout: AppConstants.authProbeTimeout,
headers: {'Authorization': 'Bearer $token'},
)).post('/auth/logout');
} catch (_) {
diff --git a/client/lib/core/config/app_constants.dart b/client/lib/core/config/app_constants.dart
new file mode 100644
index 0000000..b493956
--- /dev/null
+++ b/client/lib/core/config/app_constants.dart
@@ -0,0 +1,97 @@
+/// 全局业务常量集中地。
+///
+/// 收口此前散落在各处的「业务魔数」:网络超时、轮询间隔、重试退避、输入防抖、
+/// 分页大小、业务上限等可调参数。**不**收 UI 尺寸/间距/字号等纯样式常量
+/// (那些保留在各自 widget / 主题里,离使用点最近更易维护)。
+///
+/// 新增需要复用或可能调整的业务数值时,统一加到这里,避免再写裸字面量。
+abstract final class AppConstants {
+ // ── 网络超时 ──
+ /// 未登录公共 Dio(登录/刷新)连接超时
+ static const Duration publicConnectTimeout = Duration(seconds: 8);
+
+ /// 未登录公共 Dio 读取超时
+ static const Duration publicReceiveTimeout = Duration(seconds: 15);
+
+ /// 登录态探测/轻量请求超时
+ static const Duration authProbeTimeout = Duration(seconds: 4);
+
+ /// 异常上报请求超时
+ static const Duration errorReportTimeout = Duration(seconds: 5);
+
+ /// 检查更新请求超时
+ static const Duration updateCheckTimeout = Duration(seconds: 5);
+
+ /// 连通性探测请求超时
+ static const Duration connectivityTimeout = Duration(seconds: 15);
+
+ /// 批量导入(Excel 上传)发送超时
+ static const Duration importSendTimeout = Duration(seconds: 120);
+
+ /// 批量导入(Excel 解析入库)读取超时
+ static const Duration importReceiveTimeout = Duration(seconds: 300);
+
+ // ── 重试 / 退避 ──
+ /// 网络层错误自动重试的递增间隔(1s → 2s → 4s)
+ static const List retryBackoffs = [
+ Duration(seconds: 1),
+ Duration(seconds: 2),
+ Duration(seconds: 4),
+ ];
+
+ /// 连通性探测的多轮预算(4s → 8s → 12s)
+ static const List connectivityBudgets = [
+ Duration(seconds: 4),
+ Duration(seconds: 8),
+ Duration(seconds: 12),
+ ];
+
+ /// 连通性探测单轮(不重试)预算
+ static const List connectivitySingleBudget = [Duration(seconds: 4)];
+
+ /// 连通性探测每轮失败后的退避步长(600ms × 轮次)
+ static const Duration connectivityRetryStep = Duration(milliseconds: 600);
+
+ // ── 轮询间隔 ──
+ /// 会话心跳上报间隔
+ static const Duration heartbeatInterval = Duration(seconds: 30);
+
+ /// 在线时连通性轮询间隔
+ static const Duration connectivityOnlinePoll = Duration(seconds: 30);
+
+ /// 离线时连通性轮询间隔
+ static const Duration connectivityOfflinePoll = Duration(minutes: 1);
+
+ /// 后台检查更新轮询间隔
+ static const Duration updatePollInterval = Duration(hours: 1);
+
+ /// 启动后首次检查更新前的延迟
+ static const Duration updateInitialDelay = Duration(seconds: 3);
+
+ // ── 输入防抖 / 交互延迟 ──
+ /// 下拉框失焦自动收起延迟
+ static const Duration dropdownCloseDelay = Duration(milliseconds: 150);
+
+ /// 搜索输入防抖
+ static const Duration searchDebounce = Duration(milliseconds: 300);
+
+ /// 自更新流程各步骤间的过渡延迟
+ static const Duration updaterStepDelay = Duration(milliseconds: 400);
+
+ // ── 分页 ──
+ /// 列表默认每页条数
+ static const int defaultPageSize = 20;
+
+ /// 入库选货弹窗一次性拉取的库存条数
+ static const int stockInPickerPageSize = 500;
+
+ /// 出库选货弹窗一次性拉取的库存条数
+ static const int stockOutPickerPageSize = 1000;
+
+ /// 盘点拉取库存的每页条数
+ static const int inventoryCheckPageSize = 200;
+
+ // ── 业务上限 ──
+ /// 授权未返回时的默认设备上限
+ static const int defaultMaxDevices = 3;
+}
diff --git a/client/lib/core/errors/error_reporter.dart b/client/lib/core/errors/error_reporter.dart
index b5c5d7e..8ee022f 100644
--- a/client/lib/core/errors/error_reporter.dart
+++ b/client/lib/core/errors/error_reporter.dart
@@ -7,6 +7,7 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../config/app_config.dart';
+import '../config/app_constants.dart';
class ErrorType {
static const flutterError = 'flutter_error';
@@ -29,8 +30,8 @@ class ErrorReporter {
Dio get _client {
_dio ??= Dio(BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
- connectTimeout: const Duration(seconds: 5),
- receiveTimeout: const Duration(seconds: 5),
+ connectTimeout: AppConstants.errorReportTimeout,
+ receiveTimeout: AppConstants.errorReportTimeout,
));
return _dio!;
}
diff --git a/client/lib/core/update/app_updater_io.dart b/client/lib/core/update/app_updater_io.dart
index 32017d1..c1f4d66 100644
--- a/client/lib/core/update/app_updater_io.dart
+++ b/client/lib/core/update/app_updater_io.dart
@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
+import '../config/app_constants.dart';
// 桌面/原生平台的应用内更新实现。Web 版见 app_updater_web.dart。
@@ -34,7 +35,7 @@ Future downloadAndInstall(
await dio.download(url, exe, onReceiveProgress: prog);
// 启动 Inno Setup 安装包后退出本应用,让其覆盖安装。
await Process.start(exe, const [], mode: ProcessStartMode.detached);
- await Future.delayed(const Duration(milliseconds: 400));
+ await Future.delayed(AppConstants.updaterStepDelay);
exit(0);
}
@@ -75,7 +76,7 @@ else
fi
''');
await Process.start('/bin/sh', [script], mode: ProcessStartMode.detached);
- await Future.delayed(const Duration(milliseconds: 400));
+ await Future.delayed(AppConstants.updaterStepDelay);
exit(0);
}
diff --git a/client/lib/models/license.dart b/client/lib/models/license.dart
index 0e3c61f..c0321f6 100644
--- a/client/lib/models/license.dart
+++ b/client/lib/models/license.dart
@@ -1,3 +1,5 @@
+import '../core/config/app_constants.dart';
+
class LicenseInfo {
final int id;
final String type; // trial | monthly | annual | lifetime
@@ -20,7 +22,8 @@ class LicenseInfo {
id: (json['id'] as num?)?.toInt() ?? 0,
type: json['type'] as String? ?? 'trial',
isActive: json['is_active'] as bool? ?? false,
- maxDevices: (json['max_devices'] as num?)?.toInt() ?? 3,
+ maxDevices:
+ (json['max_devices'] as num?)?.toInt() ?? AppConstants.defaultMaxDevices,
expiresAt: json['expires_at'] != null
? DateTime.tryParse(json['expires_at'] as String)
: null,
diff --git a/client/lib/providers/connectivity_provider.dart b/client/lib/providers/connectivity_provider.dart
index 3dab0e4..34159c5 100644
--- a/client/lib/providers/connectivity_provider.dart
+++ b/client/lib/providers/connectivity_provider.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/config/app_config.dart';
+import '../core/config/app_constants.dart';
/// 每次从离线恢复到在线时自增。
/// 数据 provider 通过 watch 此值实现网络恢复后自动刷新。
@@ -44,27 +45,25 @@ class ConnectivityNotifier extends StateNotifier {
// 独立轻量 Dio:无拦截器。连接预算用 Future.timeout 逐次控制,
// 故 BaseOptions 超时放宽到 15s 作为兜底。
final _dio = Dio(BaseOptions(
- connectTimeout: const Duration(seconds: 15),
- receiveTimeout: const Duration(seconds: 15),
+ connectTimeout: AppConstants.connectivityTimeout,
+ receiveTimeout: AppConstants.connectivityTimeout,
));
/// 每次尝试的超时预算,逐次拉长(越来越长)。
- static const _budgets = [
- Duration(seconds: 4),
- Duration(seconds: 8),
- Duration(seconds: 12),
- ];
+ static const _budgets = AppConstants.connectivityBudgets;
- /// 在线时:每 30 秒轻量检测一次(单次,不重试)
+ /// 在线时:轻量检测一次(单次,不重试)
void _startOnlineTimer() {
_timer?.cancel();
- _timer = Timer.periodic(const Duration(seconds: 30), (_) => _check());
+ _timer =
+ Timer.periodic(AppConstants.connectivityOnlinePoll, (_) => _check());
}
- /// 离线时:每 1 分钟嗅探一次,降低频率节省资源
+ /// 离线时:降低频率嗅探,节省资源
void _startOfflineTimer() {
_timer?.cancel();
- _timer = Timer.periodic(const Duration(minutes: 1), (_) => _check());
+ _timer =
+ Timer.periodic(AppConstants.connectivityOfflinePoll, (_) => _check());
}
/// 立即触发一次检测(供外部调用,如 API 请求失败 / 启动时)。单次,不重试。
@@ -92,14 +91,14 @@ class ConnectivityNotifier extends StateNotifier {
/// 连通性检测。[withRetry]=true 时按 [_budgets] 退避重试,间隔递增。
Future _check({bool withRetry = false}) async {
- final budgets = withRetry ? _budgets : const [Duration(seconds: 4)];
+ final budgets = withRetry ? _budgets : AppConstants.connectivitySingleBudget;
var ok = false;
for (var i = 0; i < budgets.length; i++) {
ok = await _ping(budgets[i]);
if (ok) break;
// 最后一次失败后不再等待;中间失败按递增间隔退避(0.6s, 1.2s…)
if (i < budgets.length - 1) {
- await Future.delayed(Duration(milliseconds: 600 * (i + 1)));
+ await Future.delayed(AppConstants.connectivityRetryStep * (i + 1));
}
}
diff --git a/client/lib/providers/finance_provider.dart b/client/lib/providers/finance_provider.dart
index afe1a21..97d40d1 100644
--- a/client/lib/providers/finance_provider.dart
+++ b/client/lib/providers/finance_provider.dart
@@ -1,6 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api/api_client.dart';
import '../core/auth/auth_state.dart';
+import '../core/config/app_constants.dart';
import '../core/models/page_result.dart';
import '../models/finance.dart';
import '../repositories/finance_repository.dart';
@@ -16,7 +17,7 @@ final financeListProvider =
class FinanceListNotifier extends AsyncNotifier> {
int _page = 1;
- int _pageSize = 20;
+ int _pageSize = AppConstants.defaultPageSize;
String _type = '';
String _month = '';
diff --git a/client/lib/providers/inventory_provider.dart b/client/lib/providers/inventory_provider.dart
index 3b4a288..62225b6 100644
--- a/client/lib/providers/inventory_provider.dart
+++ b/client/lib/providers/inventory_provider.dart
@@ -1,6 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api/api_client.dart';
import '../core/auth/auth_state.dart';
+import '../core/config/app_constants.dart';
import '../core/models/page_result.dart';
import '../models/inventory.dart';
import '../repositories/inventory_repository.dart';
@@ -17,7 +18,7 @@ final inventoryListProvider =
class InventoryListNotifier extends AsyncNotifier> {
int _page = 1;
- int _pageSize = 20;
+ int _pageSize = AppConstants.defaultPageSize;
int? _warehouseId;
String _keyword = '';
List _series = [];
@@ -106,7 +107,7 @@ final inventoryLogProvider =
class InventoryLogNotifier extends AsyncNotifier> {
int _page = 1;
- int _pageSize = 20;
+ int _pageSize = AppConstants.defaultPageSize;
PageResult? _cache;
@override
diff --git a/client/lib/providers/partner_provider.dart b/client/lib/providers/partner_provider.dart
index c3d1964..17458dc 100644
--- a/client/lib/providers/partner_provider.dart
+++ b/client/lib/providers/partner_provider.dart
@@ -1,6 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api/api_client.dart';
import '../core/auth/auth_state.dart';
+import '../core/config/app_constants.dart';
import '../core/models/page_result.dart';
import '../models/partner.dart';
import '../repositories/partner_repository.dart';
@@ -25,7 +26,7 @@ final customerListProvider =
class PartnerListNotifier extends AsyncNotifier> {
final String? type;
int _page = 1;
- int _pageSize = 20;
+ int _pageSize = AppConstants.defaultPageSize;
String _keyword = '';
PageResult? _cache;
diff --git a/client/lib/providers/product_provider.dart b/client/lib/providers/product_provider.dart
index 36c3005..8b1f836 100644
--- a/client/lib/providers/product_provider.dart
+++ b/client/lib/providers/product_provider.dart
@@ -1,6 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api/api_client.dart';
import '../core/auth/auth_state.dart';
+import '../core/config/app_constants.dart';
import '../core/models/page_result.dart';
import '../models/product.dart';
import '../repositories/product_repository.dart';
@@ -17,7 +18,7 @@ final productListProvider =
class ProductListNotifier extends AsyncNotifier> {
int _page = 1;
- int _pageSize = 20;
+ int _pageSize = AppConstants.defaultPageSize;
String _keyword = '';
int? _categoryId;
PageResult? _cache;
diff --git a/client/lib/providers/session_heartbeat.dart b/client/lib/providers/session_heartbeat.dart
index b5adb8f..411ad66 100644
--- a/client/lib/providers/session_heartbeat.dart
+++ b/client/lib/providers/session_heartbeat.dart
@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api/api_client.dart';
import '../core/auth/auth_state.dart';
+import '../core/config/app_constants.dart';
import 'license_provider.dart';
/// 登录态心跳:已登录时每 30s 执行一次 **单个** POST /auth/ping:
@@ -27,7 +28,7 @@ class SessionHeartbeat {
void start() {
_timer?.cancel();
- _timer = Timer.periodic(const Duration(seconds: 30), (_) => _ping());
+ _timer = Timer.periodic(AppConstants.heartbeatInterval, (_) => _ping());
}
Future _ping() async {
diff --git a/client/lib/providers/stock_in_provider.dart b/client/lib/providers/stock_in_provider.dart
index 5b944a6..f1e994b 100644
--- a/client/lib/providers/stock_in_provider.dart
+++ b/client/lib/providers/stock_in_provider.dart
@@ -1,6 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api/api_client.dart';
import '../core/auth/auth_state.dart';
+import '../core/config/app_constants.dart';
import '../core/models/page_result.dart';
import '../models/stock_in.dart';
import '../repositories/stock_in_repository.dart';
@@ -18,7 +19,7 @@ final stockInListProvider =
class StockInListNotifier extends AsyncNotifier> {
int _page = 1;
- int _pageSize = 20;
+ int _pageSize = AppConstants.defaultPageSize;
String _status = '';
String? _startDate;
String? _endDate;
diff --git a/client/lib/providers/stock_out_provider.dart b/client/lib/providers/stock_out_provider.dart
index fb634a9..ef7b667 100644
--- a/client/lib/providers/stock_out_provider.dart
+++ b/client/lib/providers/stock_out_provider.dart
@@ -1,6 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api/api_client.dart';
import '../core/auth/auth_state.dart';
+import '../core/config/app_constants.dart';
import '../core/models/page_result.dart';
import '../models/stock_out.dart';
import '../repositories/stock_out_repository.dart';
@@ -18,7 +19,7 @@ final stockOutListProvider =
class StockOutListNotifier extends AsyncNotifier> {
int _page = 1;
- int _pageSize = 20;
+ int _pageSize = AppConstants.defaultPageSize;
String _status = '';
String? _startDate;
String? _endDate;
diff --git a/client/lib/providers/update_provider.dart b/client/lib/providers/update_provider.dart
index dcbb3e0..34d258a 100644
--- a/client/lib/providers/update_provider.dart
+++ b/client/lib/providers/update_provider.dart
@@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import '../core/config/app_config.dart';
+import '../core/config/app_constants.dart';
// ── 数据模型 ────────────────────────────────────────────────
class AppUpdateInfo {
@@ -40,8 +41,8 @@ class UpdateNotifier extends AsyncNotifier {
bool _dismissed = false; // 用户已手动关闭非强制更新提示
final _dio = Dio(BaseOptions(
- connectTimeout: const Duration(seconds: 5),
- receiveTimeout: const Duration(seconds: 5),
+ connectTimeout: AppConstants.updateCheckTimeout,
+ receiveTimeout: AppConstants.updateCheckTimeout,
));
@override
@@ -51,12 +52,12 @@ class UpdateNotifier extends AsyncNotifier {
_dio.close(force: true);
});
- // 启动后 3 秒延迟首次检查(避免与登录请求竞争)
- await Future.delayed(const Duration(seconds: 3));
+ // 启动后延迟首次检查(避免与登录请求竞争)
+ await Future.delayed(AppConstants.updateInitialDelay);
final result = await _check();
- // 每小时检查一次
- _timer = Timer.periodic(const Duration(hours: 1), (_) async {
+ // 定期检查更新
+ _timer = Timer.periodic(AppConstants.updatePollInterval, (_) async {
_dismissed = false;
final r = await _check();
state = AsyncValue.data(r);
diff --git a/client/lib/screens/auth/login_screen.dart b/client/lib/screens/auth/login_screen.dart
index 74e8064..adcc676 100644
--- a/client/lib/screens/auth/login_screen.dart
+++ b/client/lib/screens/auth/login_screen.dart
@@ -8,6 +8,7 @@ import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../core/auth/auth_state.dart';
import '../../core/config/app_config.dart';
+import '../../core/config/app_constants.dart';
import '../../core/theme/app_theme.dart';
import '../../core/storage/login_history.dart';
import '../../providers/connectivity_provider.dart';
@@ -73,7 +74,8 @@ class _LoginScreenState extends ConsumerState {
} else {
// 延迟关闭:让候选词的 onTap(pointer-up)先执行,再关闭下拉框
_closeHotelTimer?.cancel();
- _closeHotelTimer = Timer(const Duration(milliseconds: 150), _closeHotel);
+ _closeHotelTimer =
+ Timer(AppConstants.dropdownCloseDelay, _closeHotel);
}
});
_usernameFocus.addListener(() {
@@ -88,7 +90,8 @@ class _LoginScreenState extends ConsumerState {
});
} else {
_closeUsernameTimer?.cancel();
- _closeUsernameTimer = Timer(const Duration(milliseconds: 150), _closeUsername);
+ _closeUsernameTimer =
+ Timer(AppConstants.dropdownCloseDelay, _closeUsername);
}
});
}
diff --git a/client/lib/screens/finance/finance_screen.dart b/client/lib/screens/finance/finance_screen.dart
index e08742a..71ca1f5 100644
--- a/client/lib/screens/finance/finance_screen.dart
+++ b/client/lib/screens/finance/finance_screen.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/responsive/responsive.dart';
+import '../../core/config/app_constants.dart';
import '../../core/theme/app_theme.dart';
import '../../models/finance.dart';
import '../../providers/finance_provider.dart';
@@ -49,7 +50,7 @@ class _FinanceTab extends ConsumerStatefulWidget {
class _FinanceTabState extends ConsumerState<_FinanceTab> {
late String _month;
int _page = 1;
- int _pageSize = 20;
+ int _pageSize = AppConstants.defaultPageSize;
late Future> _future;
List _allRecords = [];
diff --git a/client/lib/screens/inventory/inventory_check_screen.dart b/client/lib/screens/inventory/inventory_check_screen.dart
index 255f3c0..902d590 100644
--- a/client/lib/screens/inventory/inventory_check_screen.dart
+++ b/client/lib/screens/inventory/inventory_check_screen.dart
@@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
import '../../core/theme/app_theme.dart';
import '../../models/inventory.dart';
import '../../models/warehouse.dart';
+import '../../core/config/app_constants.dart';
import '../../providers/inventory_provider.dart';
import '../../providers/warehouse_provider.dart';
@@ -51,7 +52,8 @@ class _InventoryCheckScreenState extends ConsumerState {
try {
final result = await ref
.read(inventoryRepositoryProvider)
- .listInventory(warehouseId: wh.id, pageSize: 200);
+ .listInventory(
+ warehouseId: wh.id, pageSize: AppConstants.inventoryCheckPageSize);
final items = result.data
.where((inv) => inv.productId != null)
.map((inv) => _CheckItem(inventory: inv))
diff --git a/client/lib/screens/inventory/inventory_list_screen.dart b/client/lib/screens/inventory/inventory_list_screen.dart
index 1403cf9..6c23c5f 100644
--- a/client/lib/screens/inventory/inventory_list_screen.dart
+++ b/client/lib/screens/inventory/inventory_list_screen.dart
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/config/app_config.dart';
+import '../../core/config/app_constants.dart';
import '../../core/responsive/responsive.dart';
import '../../core/theme/app_theme.dart';
import '../../models/inventory.dart';
@@ -165,8 +166,8 @@ class _InventoryListScreenState extends ConsumerState {
final token = ref.read(authStateProvider).user?.accessToken ?? '';
final dio = Dio(BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
- sendTimeout: const Duration(seconds: 120),
- receiveTimeout: const Duration(seconds: 300),
+ sendTimeout: AppConstants.importSendTimeout,
+ receiveTimeout: AppConstants.importReceiveTimeout,
));
dio.options.headers['Authorization'] = 'Bearer $token';
diff --git a/client/lib/screens/partners/partners_screen.dart b/client/lib/screens/partners/partners_screen.dart
index a1e695c..539b5f0 100644
--- a/client/lib/screens/partners/partners_screen.dart
+++ b/client/lib/screens/partners/partners_screen.dart
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/models/page_result.dart';
import '../../core/responsive/responsive.dart';
+import '../../core/config/app_constants.dart';
import '../../core/theme/app_theme.dart';
import '../../models/partner.dart';
import '../../providers/partner_provider.dart';
@@ -77,7 +78,7 @@ class _PartnersScreenState extends ConsumerState {
searchCtrl: _supplierSearchCtrl,
onSearchChanged: (v) {
_supplierDebounce?.cancel();
- _supplierDebounce = Timer(const Duration(milliseconds: 300), () {
+ _supplierDebounce = Timer(AppConstants.searchDebounce, () {
ref.read(supplierListProvider.notifier).setKeyword(v);
});
},
@@ -120,7 +121,7 @@ class _PartnersScreenState extends ConsumerState {
searchCtrl: _customerSearchCtrl,
onSearchChanged: (v) {
_customerDebounce?.cancel();
- _customerDebounce = Timer(const Duration(milliseconds: 300), () {
+ _customerDebounce = Timer(AppConstants.searchDebounce, () {
ref.read(customerListProvider.notifier).setKeyword(v);
});
},
diff --git a/client/lib/screens/settings/settings_screen.dart b/client/lib/screens/settings/settings_screen.dart
index 214e596..118adef 100644
--- a/client/lib/screens/settings/settings_screen.dart
+++ b/client/lib/screens/settings/settings_screen.dart
@@ -11,6 +11,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/auth/auth_state.dart';
import '../../widgets/write_guard.dart';
import '../../core/config/app_config.dart';
+import '../../core/config/app_constants.dart';
import '../../core/config/app_info.dart';
import '../../core/config/license_copy.dart';
import '../../core/responsive/responsive.dart';
@@ -1375,8 +1376,8 @@ class _BatchImportWidgetState extends ConsumerState<_BatchImportWidget> {
final dio = Dio(BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
- sendTimeout: const Duration(seconds: 120),
- receiveTimeout: const Duration(seconds: 300),
+ sendTimeout: AppConstants.importSendTimeout,
+ receiveTimeout: AppConstants.importReceiveTimeout,
));
dio.options.headers['Authorization'] = 'Bearer $token';
diff --git a/client/lib/screens/stock_in/stock_in_form_screen.dart b/client/lib/screens/stock_in/stock_in_form_screen.dart
index 1425174..ab8f2ec 100644
--- a/client/lib/screens/stock_in/stock_in_form_screen.dart
+++ b/client/lib/screens/stock_in/stock_in_form_screen.dart
@@ -8,6 +8,7 @@ import '../../core/auth/auth_state.dart';
import '../../core/utils/print_util.dart';
import '../../core/utils/date_util.dart';
import '../../models/stock_in.dart';
+import '../../core/config/app_constants.dart';
import '../../providers/partner_provider.dart';
import '../../providers/product_option_provider.dart';
import '../../providers/product_provider.dart';
@@ -196,7 +197,9 @@ class _StockInFormScreenState extends ConsumerState {
try {
final result = await ref
.read(inventoryRepositoryProvider)
- .listInventory(warehouseId: warehouseId, pageSize: 500);
+ .listInventory(
+ warehouseId: warehouseId,
+ pageSize: AppConstants.stockInPickerPageSize);
setState(() {
_inventoryMap = {
for (final inv in result.data.where((inv) => inv.productId != null))
diff --git a/client/lib/screens/stock_out/stock_out_form_screen.dart b/client/lib/screens/stock_out/stock_out_form_screen.dart
index da6a735..12ede4f 100644
--- a/client/lib/screens/stock_out/stock_out_form_screen.dart
+++ b/client/lib/screens/stock_out/stock_out_form_screen.dart
@@ -9,6 +9,7 @@ import '../../core/utils/print_util.dart';
import '../../core/utils/date_util.dart';
import '../../models/stock_out.dart';
import '../../widgets/date_picker_field.dart';
+import '../../core/config/app_constants.dart';
import '../../providers/inventory_provider.dart';
import '../../providers/partner_provider.dart';
import '../../providers/stock_out_provider.dart';
@@ -153,7 +154,9 @@ class _StockOutFormScreenState extends ConsumerState {
try {
final result = await ref
.read(inventoryRepositoryProvider)
- .listInventory(warehouseId: warehouseId, pageSize: 1000);
+ .listInventory(
+ warehouseId: warehouseId,
+ pageSize: AppConstants.stockOutPickerPageSize);
final Map productMap = {};
for (final inv in result.data.where((inv) => inv.productId != null)) {
final pid = inv.productId!;
diff --git a/client/macos/Runner.xcodeproj/project.pbxproj b/client/macos/Runner.xcodeproj/project.pbxproj
index 350e995..2b17df3 100644
--- a/client/macos/Runner.xcodeproj/project.pbxproj
+++ b/client/macos/Runner.xcodeproj/project.pbxproj
@@ -489,7 +489,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.example.jiuClient.RunnerTests;
+ PRODUCT_BUNDLE_IDENTIFIER = com.yanmei.jiu.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/jiu_client.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/jiu_client";
@@ -504,7 +504,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.example.jiuClient.RunnerTests;
+ PRODUCT_BUNDLE_IDENTIFIER = com.yanmei.jiu.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/jiu_client.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/jiu_client";
@@ -519,7 +519,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.example.jiuClient.RunnerTests;
+ PRODUCT_BUNDLE_IDENTIFIER = com.yanmei.jiu.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/jiu_client.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/jiu_client";
diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
index a2ec33f..96d3fee 100644
--- a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,68 +1,68 @@
{
- "images" : [
- {
- "size" : "16x16",
- "idiom" : "mac",
- "filename" : "app_icon_16.png",
- "scale" : "1x"
+ "info": {
+ "version": 1,
+ "author": "xcode"
},
- {
- "size" : "16x16",
- "idiom" : "mac",
- "filename" : "app_icon_32.png",
- "scale" : "2x"
- },
- {
- "size" : "32x32",
- "idiom" : "mac",
- "filename" : "app_icon_32.png",
- "scale" : "1x"
- },
- {
- "size" : "32x32",
- "idiom" : "mac",
- "filename" : "app_icon_64.png",
- "scale" : "2x"
- },
- {
- "size" : "128x128",
- "idiom" : "mac",
- "filename" : "app_icon_128.png",
- "scale" : "1x"
- },
- {
- "size" : "128x128",
- "idiom" : "mac",
- "filename" : "app_icon_256.png",
- "scale" : "2x"
- },
- {
- "size" : "256x256",
- "idiom" : "mac",
- "filename" : "app_icon_256.png",
- "scale" : "1x"
- },
- {
- "size" : "256x256",
- "idiom" : "mac",
- "filename" : "app_icon_512.png",
- "scale" : "2x"
- },
- {
- "size" : "512x512",
- "idiom" : "mac",
- "filename" : "app_icon_512.png",
- "scale" : "1x"
- },
- {
- "size" : "512x512",
- "idiom" : "mac",
- "filename" : "app_icon_1024.png",
- "scale" : "2x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
+ "images": [
+ {
+ "size": "16x16",
+ "idiom": "mac",
+ "filename": "app_icon_16.png",
+ "scale": "1x"
+ },
+ {
+ "size": "16x16",
+ "idiom": "mac",
+ "filename": "app_icon_32.png",
+ "scale": "2x"
+ },
+ {
+ "size": "32x32",
+ "idiom": "mac",
+ "filename": "app_icon_32.png",
+ "scale": "1x"
+ },
+ {
+ "size": "32x32",
+ "idiom": "mac",
+ "filename": "app_icon_64.png",
+ "scale": "2x"
+ },
+ {
+ "size": "128x128",
+ "idiom": "mac",
+ "filename": "app_icon_128.png",
+ "scale": "1x"
+ },
+ {
+ "size": "128x128",
+ "idiom": "mac",
+ "filename": "app_icon_256.png",
+ "scale": "2x"
+ },
+ {
+ "size": "256x256",
+ "idiom": "mac",
+ "filename": "app_icon_256.png",
+ "scale": "1x"
+ },
+ {
+ "size": "256x256",
+ "idiom": "mac",
+ "filename": "app_icon_512.png",
+ "scale": "2x"
+ },
+ {
+ "size": "512x512",
+ "idiom": "mac",
+ "filename": "app_icon_512.png",
+ "scale": "1x"
+ },
+ {
+ "size": "512x512",
+ "idiom": "mac",
+ "filename": "app_icon_1024.png",
+ "scale": "2x"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
index 641032c..6817cde 100644
Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ
diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
index dbb3483..dec9f28 100644
Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ
diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
index ccbeca1..dc89d71 100644
Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ
diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
index d75f349..ad6da14 100644
Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ
diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
index 3dfd089..02550b7 100644
Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ
diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
index 871a5cf..25fec8b 100644
Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ
diff --git a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
index d837b23..4bfa74e 100644
Binary files a/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/client/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ
diff --git a/client/macos/Runner/Configs/AppInfo.xcconfig b/client/macos/Runner/Configs/AppInfo.xcconfig
index 3c5f108..cbc3289 100644
--- a/client/macos/Runner/Configs/AppInfo.xcconfig
+++ b/client/macos/Runner/Configs/AppInfo.xcconfig
@@ -8,7 +8,7 @@
PRODUCT_NAME = jiu_client
// The application's bundle identifier
-PRODUCT_BUNDLE_IDENTIFIER = com.example.jiuClient
+PRODUCT_BUNDLE_IDENTIFIER = com.yanmei.jiu
// The copyright displayed in application information
-PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved.
+PRODUCT_COPYRIGHT = Copyright © 2026 岩美技术有限公司. All rights reserved.
diff --git a/client/macos/Runner/Info.plist b/client/macos/Runner/Info.plist
index 4789daa..7598b0b 100644
--- a/client/macos/Runner/Info.plist
+++ b/client/macos/Runner/Info.plist
@@ -13,7 +13,9 @@
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- $(PRODUCT_NAME)
+ 岩美酒库
+ CFBundleDisplayName
+ 岩美酒库
CFBundlePackageType
APPL
CFBundleShortVersionString
diff --git a/client/pubspec.lock b/client/pubspec.lock
index 0265447..81a1cf7 100644
--- a/client/pubspec.lock
+++ b/client/pubspec.lock
@@ -65,6 +65,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.0"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.4"
+ cli_util:
+ dependency: transitive
+ description:
+ name: cli_util
+ sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.2"
clock:
dependency: transitive
description:
@@ -206,6 +222,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_launcher_icons:
+ dependency: "direct dev"
+ description:
+ name: flutter_launcher_icons
+ sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.14.4"
flutter_lints:
dependency: "direct dev"
description:
@@ -381,6 +405,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.12.0"
leak_tracker:
dependency: transitive
description:
diff --git a/client/pubspec.yaml b/client/pubspec.yaml
index 44d7b2e..5a0edd5 100644
--- a/client/pubspec.yaml
+++ b/client/pubspec.yaml
@@ -37,6 +37,25 @@ dev_dependencies:
sdk: flutter
flutter_lints: ^4.0.0
http_mock_adapter: ^0.6.1
+ flutter_launcher_icons: ^0.14.1
+
+# 全平台启动图标,单一源图 assets/brand/logo_mark.png(512×512),
+# 跑 `dart run flutter_launcher_icons` 重生成 android/ios/web/windows/macos 所有图标
+flutter_launcher_icons:
+ image_path: "assets/brand/logo_mark.png"
+ android: true
+ ios: true
+ remove_alpha_ios: true
+ background_color_ios: "#FFFFFF"
+ web:
+ generate: true
+ background_color: "#FFFFFF"
+ theme_color: "#2563AC"
+ windows:
+ generate: true
+ icon_size: 256
+ macos:
+ generate: true
flutter:
uses-material-design: true
diff --git a/client/web/favicon.png b/client/web/favicon.png
index ccbeca1..dc89d71 100644
Binary files a/client/web/favicon.png and b/client/web/favicon.png differ
diff --git a/client/web/icons/Icon-192.png b/client/web/icons/Icon-192.png
index 0638d8a..9bf88e0 100644
Binary files a/client/web/icons/Icon-192.png and b/client/web/icons/Icon-192.png differ
diff --git a/client/web/icons/Icon-512.png b/client/web/icons/Icon-512.png
index 871a5cf..25fec8b 100644
Binary files a/client/web/icons/Icon-512.png and b/client/web/icons/Icon-512.png differ
diff --git a/client/web/icons/Icon-maskable-192.png b/client/web/icons/Icon-maskable-192.png
index 6af87d1..9bf88e0 100644
Binary files a/client/web/icons/Icon-maskable-192.png and b/client/web/icons/Icon-maskable-192.png differ
diff --git a/client/web/icons/Icon-maskable-512.png b/client/web/icons/Icon-maskable-512.png
index 1c7aef3..25fec8b 100644
Binary files a/client/web/icons/Icon-maskable-512.png and b/client/web/icons/Icon-maskable-512.png differ
diff --git a/client/web/index.html b/client/web/index.html
index 5b68c3d..5e67852 100644
--- a/client/web/index.html
+++ b/client/web/index.html
@@ -30,13 +30,13 @@
-
+
- 岩美
+ 岩美酒库
diff --git a/client/web/manifest.json b/client/web/manifest.json
index c3f76e2..f2a25f0 100644
--- a/client/web/manifest.json
+++ b/client/web/manifest.json
@@ -1,6 +1,6 @@
{
- "name": "岩美",
- "short_name": "岩美",
+ "name": "岩美酒库",
+ "short_name": "岩美酒库",
"start_url": ".",
"display": "standalone",
"background_color": "#2563AC",
@@ -32,4 +32,4 @@
"purpose": "maskable"
}
]
-}
+}
\ No newline at end of file
diff --git a/client/windows/runner/Runner.rc b/client/windows/runner/Runner.rc
index af4c2e7..c8a067c 100644
--- a/client/windows/runner/Runner.rc
+++ b/client/windows/runner/Runner.rc
@@ -89,13 +89,13 @@ BEGIN
BEGIN
BLOCK "040904e4"
BEGIN
- VALUE "CompanyName", "com.yanmei" "\0"
- VALUE "FileDescription", "jiu_client" "\0"
+ VALUE "CompanyName", "岩美技术有限公司" "\0"
+ VALUE "FileDescription", "岩美酒库" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "jiu_client" "\0"
- VALUE "LegalCopyright", "Copyright (C) 2026 com.yanmei. All rights reserved." "\0"
+ VALUE "LegalCopyright", "Copyright (C) 2026 岩美技术有限公司. All rights reserved." "\0"
VALUE "OriginalFilename", "jiu_client.exe" "\0"
- VALUE "ProductName", "jiu_client" "\0"
+ VALUE "ProductName", "岩美酒库" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
diff --git a/client/windows/runner/resources/app_icon.ico b/client/windows/runner/resources/app_icon.ico
index c04e20c..c9d1fb8 100644
Binary files a/client/windows/runner/resources/app_icon.ico and b/client/windows/runner/resources/app_icon.ico differ