feat(client): 登录页品牌化 + 授权过期续费入口 + 全平台应用图标统一

- 登录页文案与 Logo 改为「岩美酒库」品牌:标题/副标题/页脚 + 内嵌品牌 mark(替换原酒杯渐变方块)
- 授权过期锁定时,登录页给出续费入口:前往官网续费 + 复制客服邮箱
- 应用图标全平台统一为岩美品牌 mark:macOS(16-1024) / Web(favicon+icons+maskable)
  / Android mipmap(48-192) / iOS AppIcon(20-1024,满底无 alpha 过审核)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-19 09:58:25 +08:00
parent ca093c3c22
commit 58266a2095
35 changed files with 141 additions and 39 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

+140 -39
View File
@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../core/responsive/responsive.dart'; import '../../core/responsive/responsive.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -34,6 +35,16 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
bool _obscure = true; bool _obscure = true;
String? _errorMessage; String? _errorMessage;
// 客服邮箱(授权续费 / 锁定协助)。与官网 web/_data/site.json 的 support.email 保持一致。
static const _supportEmail = 'yammy2023@163.com';
/// 当前错误是否为「授权锁定」——后端返回固定文案
/// `license locked, please renew or contact support`(见 service.ErrLicenseLocked)。
bool get _isLicenseLocked {
final m = _errorMessage?.toLowerCase() ?? '';
return m.contains('license') && (m.contains('lock') || m.contains('expire'));
}
List<String> _hotelCodeHistory = []; List<String> _hotelCodeHistory = [];
List<String> _usernameHistory = []; List<String> _usernameHistory = [];
@@ -242,6 +253,94 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
} }
} }
Future<void> _openRenewSite() async {
await launchUrl(
Uri.parse(AppConfig.publicBaseUrl),
mode: LaunchMode.externalApplication,
);
}
Future<void> _copySupportEmail() async {
await Clipboard.setData(const ClipboardData(text: _supportEmail));
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已复制客服邮箱:$_supportEmail')),
);
}
/// 授权锁定时的可操作提示:告诉用户「去哪里解决」——前往官网续费 / 联系客服。
Widget _buildLicenseLockedNotice() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: AppTheme.danger.withAlpha(15),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: AppTheme.danger.withAlpha(80)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.lock_clock_outlined,
color: AppTheme.danger, size: 18),
SizedBox(width: 8),
Expanded(
child: Text(
'授权已过期,账号已锁定',
style: TextStyle(
color: AppTheme.danger,
fontSize: 14,
fontWeight: FontWeight.w600),
),
),
],
),
const SizedBox(height: 6),
const Text(
'当前门店授权已到期,暂时无法登录。请续费授权后重试,或联系客服协助开通。',
style: TextStyle(
color: AppTheme.textSecondary, fontSize: 13, height: 1.4),
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton.icon(
onPressed: _openRenewSite,
icon: const Icon(Icons.open_in_new, size: 16),
label: const Text('前往官网续费'),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.danger,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4)),
),
),
OutlinedButton.icon(
onPressed: _copySupportEmail,
icon: const Icon(Icons.support_agent_outlined, size: 16),
label: const Text('复制客服邮箱'),
style: OutlinedButton.styleFrom(
foregroundColor: AppTheme.danger,
side: BorderSide(color: AppTheme.danger.withAlpha(120)),
padding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4)),
),
),
],
),
],
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// 被踢下线 / 会话失效:登出后跳回登录页,弹一次提示并清空 // 被踢下线 / 会话失效:登出后跳回登录页,弹一次提示并清空
@@ -283,20 +382,12 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// Logo // Logo(岩美酒库品牌 mark,与 App 图标一致)
Container( Container(
width: 80, width: 80,
height: 80, height: 80,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( borderRadius: BorderRadius.circular(15),
colors: [
AppTheme.primaryLight,
AppTheme.primaryDark
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppTheme.primary.withAlpha(102), color: AppTheme.primary.withAlpha(102),
@@ -305,12 +396,19 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
), ),
], ],
), ),
child: const Icon(Icons.wine_bar, child: ClipRRect(
color: Colors.white, size: 44), borderRadius: BorderRadius.circular(15),
child: Image.asset(
'assets/brand/logo_mark.png',
width: 80,
height: 80,
fit: BoxFit.cover,
),
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
const Text( const Text(
'酒库管理系统', '岩美酒库',
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
@@ -320,7 +418,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
const Text( const Text(
'酒店仓库管理解决方案', '专业酒水仓储 · 进销存一体化管理',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: AppTheme.textSecondary), color: AppTheme.textSecondary),
@@ -448,32 +546,35 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
// Inline error // Inline error
if (_errorMessage != null) ...[ if (_errorMessage != null) ...[
const SizedBox(height: 12), const SizedBox(height: 12),
Container( if (_isLicenseLocked)
width: double.infinity, _buildLicenseLockedNotice()
padding: const EdgeInsets.symmetric( else
horizontal: 12, vertical: 10), Container(
decoration: BoxDecoration( width: double.infinity,
color: AppTheme.danger.withAlpha(15), padding: const EdgeInsets.symmetric(
borderRadius: BorderRadius.circular(4), horizontal: 12, vertical: 10),
border: Border.all( decoration: BoxDecoration(
color: AppTheme.danger.withAlpha(80)), color: AppTheme.danger.withAlpha(15),
), borderRadius: BorderRadius.circular(4),
child: Row( border: Border.all(
children: [ color: AppTheme.danger.withAlpha(80)),
const Icon(Icons.error_outline, ),
color: AppTheme.danger, size: 16), child: Row(
const SizedBox(width: 8), children: [
Expanded( const Icon(Icons.error_outline,
child: Text( color: AppTheme.danger, size: 16),
_errorMessage!, const SizedBox(width: 8),
style: const TextStyle( Expanded(
color: AppTheme.danger, child: Text(
fontSize: 13), _errorMessage!,
style: const TextStyle(
color: AppTheme.danger,
fontSize: 13),
),
), ),
), ],
], ),
), ),
),
], ],
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -530,7 +631,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
right: 0, right: 0,
child: Center( child: Center(
child: Text( child: Text(
'© 2026 酒库管理系统 v1.0.0', '© 2026 岩美酒库 v1.0.0',
style: TextStyle( style: TextStyle(
color: Colors.white.withAlpha(102), fontSize: 12), color: Colors.white.withAlpha(102), fontSize: 12),
), ),
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

+1
View File
@@ -44,3 +44,4 @@ flutter:
assets: assets:
- assets/fonts/NotoSansSC-Regular.ttf - assets/fonts/NotoSansSC-Regular.ttf
- assets/config/app_info.json - assets/config/app_info.json
- assets/brand/logo_mark.png
Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB