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 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../core/responsive/responsive.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
@@ -34,6 +35,16 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
bool _obscure = true;
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> _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
Widget build(BuildContext context) {
// 被踢下线 / 会话失效:登出后跳回登录页,弹一次提示并清空
@@ -283,20 +382,12 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Logo
// Logo(岩美酒库品牌 mark,与 App 图标一致)
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
AppTheme.primaryLight,
AppTheme.primaryDark
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: AppTheme.primary.withAlpha(102),
@@ -305,12 +396,19 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
),
],
),
child: const Icon(Icons.wine_bar,
color: Colors.white, size: 44),
child: ClipRRect(
borderRadius: BorderRadius.circular(15),
child: Image.asset(
'assets/brand/logo_mark.png',
width: 80,
height: 80,
fit: BoxFit.cover,
),
),
),
const SizedBox(height: 20),
const Text(
'酒库管理系统',
'岩美酒库',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w700,
@@ -320,7 +418,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
),
const SizedBox(height: 6),
const Text(
'酒店仓库管理解决方案',
'专业酒水仓储 · 进销存一体化管理',
style: TextStyle(
fontSize: 13,
color: AppTheme.textSecondary),
@@ -448,32 +546,35 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
// Inline error
if (_errorMessage != null) ...[
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: AppTheme.danger.withAlpha(15),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: AppTheme.danger.withAlpha(80)),
),
child: Row(
children: [
const Icon(Icons.error_outline,
color: AppTheme.danger, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
_errorMessage!,
style: const TextStyle(
color: AppTheme.danger,
fontSize: 13),
if (_isLicenseLocked)
_buildLicenseLockedNotice()
else
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: AppTheme.danger.withAlpha(15),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: AppTheme.danger.withAlpha(80)),
),
child: Row(
children: [
const Icon(Icons.error_outline,
color: AppTheme.danger, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
_errorMessage!,
style: const TextStyle(
color: AppTheme.danger,
fontSize: 13),
),
),
),
],
],
),
),
),
],
const SizedBox(height: 24),
@@ -530,7 +631,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
right: 0,
child: Center(
child: Text(
'© 2026 酒库管理系统 v1.0.0',
'© 2026 岩美酒库 v1.0.0',
style: TextStyle(
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/fonts/NotoSansSC-Regular.ttf
- 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