feat(client): 移动端布局适配 + 信息架构整理

- 入库/出库详情页:窄屏商品明细切换为卡片竖排,顶部操作收入溢出菜单
- 基础数据:新增「仓库」tab,使用 DataTableCard + mobileCards 适配窄屏
- 系统设置:移除仓库 tab(已迁入基础数据),新增「授权」tab,数据导入改名「数据管理」
- 关于我们:移除授权信息,补充帮助与文档/扫码防伪/法律信息/系统信息等模块
- AppInfo 新增 phone/wechat/termsUrl/privacyUrl/docsUrl 字段(可选,空则不渲染)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-07 20:16:34 +08:00
parent e550b73d6d
commit ced407ea87
7 changed files with 975 additions and 678 deletions
+119 -109
View File
@@ -1,9 +1,10 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../core/responsive/responsive.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.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_info.dart';
@@ -11,10 +12,9 @@ import '../../core/theme/app_theme.dart';
import '../../core/update/app_updater.dart';
import '../../core/utils/dialog_util.dart';
import '../../providers/feedback_provider.dart';
import '../../providers/license_provider.dart';
import '../../providers/update_provider.dart';
/// 「关于我们」独立页面(左侧菜单项)。原为系统设置里的「关于」Tab。
/// 「关于我们」独立页面(左侧菜单项)。
class AboutScreen extends ConsumerStatefulWidget {
const AboutScreen({super.key});
@@ -27,7 +27,6 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
Widget build(BuildContext context) {
final appVersion = ref.watch(appVersionProvider).valueOrNull ?? 'v1.0.0';
final updateInfo = ref.watch(updateProvider).valueOrNull;
final licenseAsync = ref.watch(licenseProvider);
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
@@ -80,76 +79,33 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
),
const SizedBox(height: 20),
// ── 授权信息 ──
// ── 帮助与文档 ──
_AboutSection(
title: '授权信息',
title: '帮助与文档',
children: [
licenseAsync.when(
loading: () => const _AboutRow(label: '授权状态', value: '加载中…'),
error: (_, __) =>
const _AboutRow(label: '授权状态', value: '暂无授权信息'),
data: (lic) {
if (lic == null) {
return const _AboutRow(label: '授权状态', value: '未激活');
}
return Column(
children: [
_AboutRow(label: '授权类型', value: lic.typeLabel),
_AboutRow(
label: '授权状态',
value: lic.isExpired
? '已过期'
: lic.isActive
? '正常'
: '已停用',
valueColor: lic.isExpired
? AppTheme.danger
: lic.isActive
? AppTheme.success
: AppTheme.textSecondary,
),
if (lic.expiresAt != null)
_AboutRow(
label: '到期时间',
value: DateFormat('yyyy-MM-dd').format(lic.expiresAt!),
trailing: lic.daysRemaining != null &&
lic.daysRemaining! <= 30
? Chip(
label: Text(
lic.isExpired
? '已过期'
: '剩余 ${lic.daysRemaining}',
style: const TextStyle(
fontSize: 11, color: Colors.white),
),
backgroundColor: lic.isExpired
? AppTheme.danger
: Colors.orange,
padding: EdgeInsets.zero,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
)
: null,
)
else
const _AboutRow(label: '到期时间', value: '永久有效'),
if (lic.activatedAt != null)
_AboutRow(
label: '激活时间',
value: DateFormat('yyyy-MM-dd')
.format(lic.activatedAt!),
),
],
);
},
),
const _AboutRow(label: '使用手册', value: '查看产品使用文档与操作指南'),
const SizedBox(height: 8),
Row(
Wrap(
spacing: 8,
children: [
OutlinedButton.icon(
onPressed: () => _showRenewDialog(),
icon: const Icon(Icons.card_membership, size: 16),
label: const Text('续费 / 升级授权'),
onPressed: () async {
final url = Uri.parse(
AppInfo.docsUrl.isNotEmpty
? AppInfo.docsUrl
: '${AppInfo.website}/docs/');
if (await canLaunchUrl(url)) launchUrl(url);
},
icon: const Icon(Icons.menu_book_outlined, size: 16),
label: const Text('打开文档'),
),
OutlinedButton.icon(
onPressed: () async {
final url = Uri.parse('${AppInfo.website}/downloads/');
if (await canLaunchUrl(url)) launchUrl(url);
},
icon: const Icon(Icons.history_outlined, size: 16),
label: const Text('更新日志'),
),
],
),
@@ -162,9 +118,25 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
title: '关于我们',
children: [
_AboutRow(label: '服务商', value: AppInfo.provider),
_AboutRow(label: '官方网站', value: AppInfo.website),
_AboutRow(
label: '官方网站',
value: AppInfo.website,
trailing: AppInfo.website.isNotEmpty
? TextButton(
onPressed: () async {
final uri = Uri.parse(AppInfo.website);
if (await canLaunchUrl(uri)) launchUrl(uri);
},
child: const Text('访问', style: TextStyle(fontSize: 12)),
)
: null,
),
_AboutRow(label: '联系邮箱', value: AppInfo.email),
const _AboutRow(label: '技术支持', value: '周一至周五 9:00 - 18:00'),
if (AppInfo.phone.isNotEmpty)
_AboutRow(label: '联系电话', value: AppInfo.phone),
if (AppInfo.wechat.isNotEmpty)
_AboutRow(label: '微信', value: AppInfo.wechat),
const _AboutRow(label: '服务时间', value: '周一至周五 9:0018:00'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
@@ -184,6 +156,81 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
),
const SizedBox(height: 20),
// ── 扫码防伪 ──
const _AboutSection(
title: '扫码防伪',
children: [
_AboutRow(
label: '功能说明',
value: '每件商品可生成专属二维码,顾客扫码即可查看商品名称、系列、规格及批次信息,帮助验证商品真实性。',
),
],
),
const SizedBox(height: 20),
// ── 法律信息 ──
_AboutSection(
title: '法律信息',
children: [
if (AppInfo.termsUrl.isNotEmpty)
_AboutRow(
label: '服务条款',
value: '查看服务条款',
trailing: TextButton(
onPressed: () async {
final uri = Uri.parse(AppInfo.termsUrl);
if (await canLaunchUrl(uri)) launchUrl(uri);
},
child: const Text('查看', style: TextStyle(fontSize: 12)),
),
),
if (AppInfo.privacyUrl.isNotEmpty)
_AboutRow(
label: '隐私政策',
value: '查看隐私政策',
trailing: TextButton(
onPressed: () async {
final uri = Uri.parse(AppInfo.privacyUrl);
if (await canLaunchUrl(uri)) launchUrl(uri);
},
child: const Text('查看', style: TextStyle(fontSize: 12)),
),
),
_AboutRow(label: '版权', value: '© ${DateTime.now().year} ${AppInfo.provider}'),
],
),
const SizedBox(height: 20),
// ── 系统信息 ──
_AboutSection(
title: '系统信息',
children: [
_AboutRow(
label: '运行平台',
value: kIsWeb
? 'Web'
: Platform.isAndroid
? 'Android'
: Platform.isIOS
? 'iOS'
: Platform.isMacOS
? 'macOS'
: Platform.isWindows
? 'Windows'
: Platform.operatingSystem,
),
_AboutRow(label: '应用版本', value: appVersion),
FutureBuilder<PackageInfo>(
future: PackageInfo.fromPlatform(),
builder: (ctx, snap) => _AboutRow(
label: '构建号',
value: snap.data?.buildNumber ?? '-',
),
),
],
),
const SizedBox(height: 20),
// ── 意见反馈 ──
_AboutSection(
title: '意见反馈',
@@ -215,43 +262,6 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
);
}
void _showRenewDialog() {
showAppDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('续费 / 升级授权'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('请联系我们获取续费报价:'),
const SizedBox(height: 12),
SelectableText('📧 ${AppInfo.email}',
style: const TextStyle(fontSize: 13)),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('关闭'),
),
ElevatedButton(
onPressed: () async {
await Clipboard.setData(ClipboardData(text: AppInfo.email));
if (ctx.mounted) {
Navigator.pop(ctx);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('邮箱已复制到剪贴板')),
);
}
},
child: const Text('复制邮箱'),
),
],
),
);
}
void _showFeedbackDialog({required bool isBug}) {
showAppDialog(
context: context,