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