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>
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 495 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 895 B |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 686 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 895 B |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -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),
|
||||
),
|
||||
|
||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 669 B |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -44,3 +44,4 @@ flutter:
|
||||
assets:
|
||||
- assets/fonts/NotoSansSC-Regular.ttf
|
||||
- assets/config/app_info.json
|
||||
- assets/brand/logo_mark.png
|
||||
|
||||
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 669 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 7.8 KiB |