chore: release v1.0.24
Deploy / build-linux-web (push) Successful in 59s
Deploy / build-windows (push) Successful in 1m50s
Deploy / build-macos (push) Successful in 1m22s
Deploy / build-android (push) Successful in 1m26s
Deploy / build-ios (push) Successful in 7s
Deploy / release-deploy (push) Successful in 1m39s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-08 07:31:55 +08:00
parent 22f07ed805
commit 1ec1d4209a
13 changed files with 832 additions and 172 deletions
@@ -2,6 +2,7 @@ import 'dart:math' as math;
import 'package:dio/dio.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../core/config/app_config.dart';
@@ -119,6 +120,7 @@ class _PublicProductScreenState extends State<PublicProductScreen> {
final descriptionKeywords = (d['description_keywords'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ?? const <String>[];
final salePrice = (d['sale_price'] as num?)?.toDouble() ?? 0.0;
final origin = d['origin'] as String? ?? '';
final shelfLife = d['shelf_life'] as String? ?? '无限期(适饮)';
final storage = d['storage'] as String? ?? '阴凉干燥、避光保存';
@@ -153,7 +155,12 @@ class _PublicProductScreenState extends State<PublicProductScreen> {
unit: unit,
),
),
// 4. Quick specs (only if we could parse any)
// 4a. Sale price badge (only when price > 0)
if (salePrice > 0)
SliverToBoxAdapter(
child: _PriceBadge(price: salePrice),
),
// 4b. Quick specs (only if we could parse any)
if (quickSpecs.isNotEmpty)
SliverToBoxAdapter(
child: _QuickSpecs(items: quickSpecs),
@@ -607,6 +614,43 @@ class _VerifiedRibbon extends StatelessWidget {
}
}
// ── Price Badge ───────────────────────────────────────────────────────
class _PriceBadge extends StatelessWidget {
final double price;
const _PriceBadge({required this.price});
@override
Widget build(BuildContext context) {
final priceStr = price % 1 == 0
? price.toInt().toString()
: price.toStringAsFixed(2);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: const BoxDecoration(
color: _kPaper,
border: Border(bottom: BorderSide(color: _kBorder)),
),
child: Row(
children: [
const Text('建议零售价',
style: TextStyle(fontSize: 13, color: _kTextMid)),
const Spacer(),
Text(
'¥$priceStr',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w700,
color: _kBurgundy,
letterSpacing: 0.04,
),
),
],
),
);
}
}
// ── Title Block ───────────────────────────────────────────────────────
class _TitleBlock extends StatelessWidget {
@@ -1187,6 +1231,7 @@ class _ShopCard extends StatelessWidget {
final address = shop['address'] as String? ?? '';
final phone = shop['phone'] as String? ?? '';
final hours = shop['business_hours'] as String? ?? '';
final wechatId = shop['wechat_id'] as String? ?? '';
// Avatar text: first 2 chars of shop name
final avatarText = name.length >= 2 ? name.substring(0, 2) : name;
@@ -1249,7 +1294,10 @@ class _ShopCard extends StatelessWidget {
SizedBox(
width: double.infinity, height: 44,
child: ElevatedButton(
onPressed: () {},
onPressed: code.isNotEmpty
? () => context.push(
'/shop/$code?shopName=${Uri.encodeComponent(name)}')
: null,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0A1F3B),
foregroundColor: Colors.white,
@@ -1260,27 +1308,58 @@ class _ShopCard extends StatelessWidget {
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, letterSpacing: 0.04)),
),
),
const SizedBox(height: 10),
SizedBox(
width: double.infinity, height: 40,
child: OutlinedButton.icon(
onPressed: () {},
style: OutlinedButton.styleFrom(
foregroundColor: _kInkDeep,
side: const BorderSide(color: _kBorder, width: 1.5),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
if (wechatId.isNotEmpty) ...[
const SizedBox(height: 10),
SizedBox(
width: double.infinity, height: 40,
child: OutlinedButton.icon(
onPressed: () => _showWechatDialog(context, wechatId),
style: OutlinedButton.styleFrom(
foregroundColor: _kInkDeep,
side: const BorderSide(color: _kBorder, width: 1.5),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
icon: const Icon(Icons.person_outline, size: 16),
label: const Text('添加门店微信',
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)),
),
icon: const Icon(Icons.person_outline, size: 16),
label: const Text('添加门店微信',
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)),
),
),
],
],
),
);
}
}
void _showWechatDialog(BuildContext context, String wechatId) {
showDialog<void>(
context: context,
builder: (_) => AlertDialog(
title: const Text('门店微信号', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.wechat, size: 48, color: Color(0xFF07C160)),
const SizedBox(height: 12),
SelectableText(
wechatId,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, letterSpacing: 0.5),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
const Text('长按上方微信号可复制', style: TextStyle(fontSize: 12, color: _kTextMid)),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('关闭'),
),
],
),
);
}
class _ShopInfoRow extends StatelessWidget {
final IconData icon;
final String text;