feat(client): 库存抽屉打印标签 + 修本地构建版本号不对
- 商品编辑抽屉头部「预览」旁加「打印标签」(同入库管理:QR+品牌型号版本+ 门店信息 → LabelPreviewDialog;移动端无打印不渲染) - 修版本号 bug:appVersionProvider/更新检查改为优先 APP_VERSION dart-define (本地与 iOS 手工构建不改 pubspec,PackageInfo 恒为旧值 1.0.52,App 内 显示错且误弹更新横幅);版本比较容忍 -dev/+build 后缀(每段取前导数字) - local_test.sh:版本标记 -dev(如 v1.1.4-dev)明确区分本地包;新增 --ios 模式(开发签名构建 + devicectl 装 USB iPhone),双端同一版本注入链路 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -95,8 +95,8 @@ class UpdateNotifier extends AsyncNotifier<AppUpdateInfo?> {
|
|||||||
final downloadUrls =
|
final downloadUrls =
|
||||||
rawUrls.map((k, v) => MapEntry(k, v?.toString() ?? ''));
|
rawUrls.map((k, v) => MapEntry(k, v?.toString() ?? ''));
|
||||||
|
|
||||||
final info = await PackageInfo.fromPlatform();
|
final current = (await currentAppVersion()).substring(1); // 去 v 前缀
|
||||||
final hasUpdate = _isNewer(latestVersion, info.version);
|
final hasUpdate = _isNewer(latestVersion, current);
|
||||||
|
|
||||||
return AppUpdateInfo(
|
return AppUpdateInfo(
|
||||||
latestVersion: latestVersion,
|
latestVersion: latestVersion,
|
||||||
@@ -123,16 +123,32 @@ class UpdateNotifier extends AsyncNotifier<AppUpdateInfo?> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<int> _parse(String v) {
|
List<int> _parse(String v) {
|
||||||
final parts = v.split('.').map((s) => int.tryParse(s) ?? 0).toList();
|
// 每段取前导数字(容忍 1.1.4-dev / 1.1.4+7 等本地构建后缀)
|
||||||
|
final parts = v.split('.').map((s) {
|
||||||
|
final m = RegExp(r'^\d+').firstMatch(s);
|
||||||
|
return m == null ? 0 : int.parse(m.group(0)!);
|
||||||
|
}).toList();
|
||||||
while (parts.length < 3) parts.add(0);
|
while (parts.length < 3) parts.add(0);
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 版本号 Provider(供状态栏 / 门店信息面板使用)──────────
|
// ── 当前版本(单一真相)─────────────────────────────────────
|
||||||
final appVersionProvider = FutureProvider<String>((ref) async {
|
// 优先编译期 APP_VERSION dart-define(CI 与本地脚本都传;本地构建不改
|
||||||
|
// pubspec,PackageInfo 会是旧值);未定义时回退 PackageInfo。
|
||||||
|
const _definedVersion = String.fromEnvironment('APP_VERSION');
|
||||||
|
|
||||||
|
Future<String> currentAppVersion() async {
|
||||||
|
if (_definedVersion.isNotEmpty) {
|
||||||
|
return _definedVersion.startsWith('v') ? _definedVersion : 'v$_definedVersion';
|
||||||
|
}
|
||||||
final info = await PackageInfo.fromPlatform();
|
final info = await PackageInfo.fromPlatform();
|
||||||
return 'v${info.version}';
|
return 'v${info.version}';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 版本号 Provider(供状态栏 / 门店信息面板使用)──────────
|
||||||
|
final appVersionProvider = FutureProvider<String>((ref) async {
|
||||||
|
return currentAppVersion();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 打开下载链接工具函数 ────────────────────────────────────
|
// ── 打开下载链接工具函数 ────────────────────────────────────
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:image_picker/image_picker.dart';
|
|||||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||||
|
|
||||||
import '../core/config/app_config.dart';
|
import '../core/config/app_config.dart';
|
||||||
|
import '../core/utils/dialog_util.dart';
|
||||||
import '../core/responsive/responsive.dart';
|
import '../core/responsive/responsive.dart';
|
||||||
import '../core/theme/app_chrome.g.dart';
|
import '../core/theme/app_chrome.g.dart';
|
||||||
import '../core/theme/app_dims.g.dart';
|
import '../core/theme/app_dims.g.dart';
|
||||||
@@ -20,11 +21,14 @@ import '../models/product.dart';
|
|||||||
import '../models/product_image.dart';
|
import '../models/product_image.dart';
|
||||||
import '../providers/product_option_provider.dart';
|
import '../providers/product_option_provider.dart';
|
||||||
import '../providers/product_provider.dart';
|
import '../providers/product_provider.dart';
|
||||||
|
import '../providers/shop_provider.dart' show shopInfoProvider;
|
||||||
import 'ds/ds_atoms.dart';
|
import 'ds/ds_atoms.dart';
|
||||||
import 'ds/grid_combo_cell.dart';
|
import 'ds/grid_combo_cell.dart';
|
||||||
import 'ds/m_sheet.dart';
|
import 'ds/m_sheet.dart';
|
||||||
import 'searchable_option_field.dart';
|
import 'searchable_option_field.dart';
|
||||||
import 'fullscreen_image_viewer.dart';
|
import 'fullscreen_image_viewer.dart';
|
||||||
|
import '../core/utils/label_data.dart';
|
||||||
|
import 'label_preview_dialog.dart';
|
||||||
import 'write_guard.dart';
|
import 'write_guard.dart';
|
||||||
import 'ds/ds_toast.dart';
|
import 'ds/ds_toast.dart';
|
||||||
|
|
||||||
@@ -184,6 +188,14 @@ class _ProductEditorDrawerState extends ConsumerState<_ProductEditorDrawer> {
|
|||||||
onPressed: p == null || p.publicId.isEmpty
|
onPressed: p == null || p.publicId.isEmpty
|
||||||
? null
|
? null
|
||||||
: () => context.push('/product/${p.publicId}')),
|
: () => context.push('/product/${p.publicId}')),
|
||||||
|
// 打印标签(同入库管理打印标签;移动端无打印——用户拍板)
|
||||||
|
if (!context.isMobile) ...[
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
DsButton('打印标签',
|
||||||
|
icon: LucideIcons.printer,
|
||||||
|
small: true,
|
||||||
|
onPressed: p == null ? null : () => _printLabel(p)),
|
||||||
|
],
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => Navigator.of(context).pop(),
|
onTap: () => Navigator.of(context).pop(),
|
||||||
@@ -615,6 +627,31 @@ class _ProductEditorDrawerState extends ConsumerState<_ProductEditorDrawer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── 保存:介绍正文 +(选了的话)介绍库文档 ──
|
// ── 保存:介绍正文 +(选了的话)介绍库文档 ──
|
||||||
|
/// 打印标签(同入库管理打印标签:QR + 品牌/型号/版本 + 门店信息)。
|
||||||
|
Future<void> _printLabel(Product p) async {
|
||||||
|
final qrBytes =
|
||||||
|
await ref.read(productRepositoryProvider).getQRCodeBytes(p.id);
|
||||||
|
final shopInfo = ref.read(shopInfoProvider).valueOrNull;
|
||||||
|
if (!mounted) return;
|
||||||
|
final label = LabelData(
|
||||||
|
productId: p.id,
|
||||||
|
qrBytes: qrBytes,
|
||||||
|
name: p.name,
|
||||||
|
code: p.code,
|
||||||
|
series: (p.series ?? '').isEmpty ? null : p.series,
|
||||||
|
spec: (p.spec ?? '').isEmpty ? null : p.spec,
|
||||||
|
remark: (p.remark ?? '').isEmpty ? null : p.remark,
|
||||||
|
shopName: shopInfo?.name ?? '',
|
||||||
|
shopAddress: shopInfo?.address ?? '',
|
||||||
|
shopPhone: shopInfo?.phone ?? '',
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
showAppDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => LabelPreviewDialog(labels: [label]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _save() async {
|
Future<void> _save() async {
|
||||||
setState(() => _saving = true);
|
setState(() => _saving = true);
|
||||||
try {
|
try {
|
||||||
|
|||||||
+46
-11
@@ -1,17 +1,19 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# local_test.sh — 本地编译一份「指向线上后端」的 macOS app 并启动,用于发版前自测。
|
# local_test.sh — 本地编译一份「指向线上后端」的 app 用于发版前自测。
|
||||||
#
|
#
|
||||||
# 用法:
|
# 用法:
|
||||||
# sh scripts/local_test.sh # 编译 + 启动
|
# sh scripts/local_test.sh # 编译 macOS + 启动
|
||||||
# sh scripts/local_test.sh --no-open # 只编译不启动
|
# sh scripts/local_test.sh --no-open # 只编译 macOS 不启动
|
||||||
|
# sh scripts/local_test.sh --ios # 编译 iOS 并装到 USB iPhone(开发签名)
|
||||||
#
|
#
|
||||||
# 后端固定指向生产 https://jiu.51yanmei.com(与 CI compile-macos.sh 一致;
|
# 后端固定指向生产 https://jiu.51yanmei.com(与 CI compile 脚本一致)。
|
||||||
# 2026-07-03 备案通过已回切 https 域名)。
|
# 版本号 = 最新 client-v* tag 的 patch+1 加 -dev 后缀(如 v1.1.4-dev),
|
||||||
|
# App 内显示走 APP_VERSION dart-define(update_provider.currentAppVersion),
|
||||||
|
# 与 CI 同一读取链路——本地构建不再显示 pubspec 旧版本(2026-07-04 修复)。
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
export PATH="/opt/homebrew/bin:$PATH"
|
export PATH="/opt/homebrew/bin:$PATH"
|
||||||
|
|
||||||
# 版本号:取最新 client-v* tag 的 patch+1,纯展示用,取不到则回退 0.0.0-dev。
|
|
||||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
@@ -20,19 +22,52 @@ if [ -n "$LATEST_TAG" ]; then
|
|||||||
BASE_VER="${LATEST_TAG#client-v}"
|
BASE_VER="${LATEST_TAG#client-v}"
|
||||||
MAJOR_MINOR="${BASE_VER%.*}"
|
MAJOR_MINOR="${BASE_VER%.*}"
|
||||||
PATCH="${BASE_VER##*.}"
|
PATCH="${BASE_VER##*.}"
|
||||||
VER="${MAJOR_MINOR}.$((PATCH + 1))"
|
VER="${MAJOR_MINOR}.$((PATCH + 1))-dev"
|
||||||
else
|
else
|
||||||
VER="0.0.0-dev"
|
VER="0.0.0-dev"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
DEFINES=(
|
||||||
|
"--dart-define=BASE_URL=https://jiu.51yanmei.com"
|
||||||
|
"--dart-define=PUBLIC_URL=https://jiu.51yanmei.com"
|
||||||
|
"--dart-define=APP_VERSION=v${VER}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--ios" ]; then
|
||||||
|
# ── iOS:开发签名构建 + devicectl 装到 USB iPhone ──
|
||||||
|
# Release 配置是 CI 的 Manual+App Store 签名,本机没有分发证书,
|
||||||
|
# 故 xcodebuild 用 Automatic + Apple Development 覆盖(不改工程文件)。
|
||||||
|
echo "==> 编译 iOS(线上后端,版本 v${VER})"
|
||||||
|
cd client
|
||||||
|
flutter build ios --release --config-only "${DEFINES[@]}"
|
||||||
|
cd ios
|
||||||
|
CD_ID="$(xcrun devicectl list devices 2>/dev/null | grep 'available (paired)' | grep 'iPhone' | grep -oE '[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}' | head -1 || true)"
|
||||||
|
if [ -z "$CD_ID" ]; then
|
||||||
|
echo "!! 未检测到已配对 iPhone(插线并解锁后重试)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
xcodebuild -workspace Runner.xcworkspace -scheme Runner -configuration Release \
|
||||||
|
-destination "generic/platform=iOS" \
|
||||||
|
CODE_SIGN_STYLE=Automatic DEVELOPMENT_TEAM=BYL4KQHMTN \
|
||||||
|
CODE_SIGN_IDENTITY="Apple Development" PROVISIONING_PROFILE_SPECIFIER= \
|
||||||
|
-allowProvisioningUpdates -allowProvisioningDeviceRegistration build \
|
||||||
|
| grep -E "BUILD (SUCCEEDED|FAILED)"
|
||||||
|
APP_PATH="$(find "$HOME/Library/Developer/Xcode/DerivedData" -path '*Release-iphoneos/Runner.app' -maxdepth 6 2>/dev/null | head -1)"
|
||||||
|
if [ -z "$APP_PATH" ]; then
|
||||||
|
echo "!! 找不到构建产物 Runner.app" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "==> 安装到 iPhone($CD_ID)"
|
||||||
|
xcrun devicectl device install app --device "$CD_ID" "$APP_PATH" | grep -m1 databaseSequenceNumber || true
|
||||||
|
echo "==> 完成:解锁手机打开 岩美酒库(版本 v${VER})"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
APP="client/build/macos/Build/Products/Release/jiu_client.app"
|
APP="client/build/macos/Build/Products/Release/jiu_client.app"
|
||||||
|
|
||||||
echo "==> 编译 macOS app(线上后端,版本 v${VER})"
|
echo "==> 编译 macOS app(线上后端,版本 v${VER})"
|
||||||
cd client
|
cd client
|
||||||
flutter build macos --release \
|
flutter build macos --release "${DEFINES[@]}"
|
||||||
"--dart-define=BASE_URL=https://jiu.51yanmei.com" \
|
|
||||||
"--dart-define=PUBLIC_URL=https://jiu.51yanmei.com" \
|
|
||||||
"--dart-define=APP_VERSION=v${VER}"
|
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
echo "==> 产物:$APP"
|
echo "==> 产物:$APP"
|
||||||
|
|||||||
Reference in New Issue
Block a user