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 '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),
|
||||||
),
|
),
|
||||||
|
|||||||
|
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:
|
||||||
- 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
|
||||||
|
|||||||
|
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 |