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:
@@ -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:00–18: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,
|
||||
|
||||
Reference in New Issue
Block a user