Compare commits

..

1 Commits

Author SHA1 Message Date
wangjia 648d9bc0d5 chore: release client-v1.0.62
Deploy Client / build-client-web (push) Successful in 40s
Deploy Client / build-macos (push) Successful in 2m0s
Deploy Client / build-android (push) Successful in 1m9s
Deploy Client / build-ios (push) Successful in 2m30s
Deploy Client / build-windows (push) Successful in 2m16s
Deploy Client / release-deploy-client (push) Successful in 1m23s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 10:14:44 +08:00
4 changed files with 75 additions and 55 deletions
+8
View File
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.62] - 2026-06-20
### 改进
- 入库单、出库单的仓库、供应商、客户下拉框改为可搜索:点击后弹出搜索框,支持按名称、拼音首字母或编码快速筛选,选项多时无需再逐条翻找
### 修复
- 修复账号在别处登录或被管理员强制下线后、退回登录页却没有任何提示的问题,现在会弹窗明确告知「登录已失效」
## [1.0.61] - 2026-06-20
### 改进
+31 -5
View File
@@ -94,6 +94,34 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
Timer(AppConstants.dropdownCloseDelay, _closeUsername);
}
});
// 处理「进入登录页之前」就已置入的会话失效提示:登出在 login_screen 挂载前就
// 设好了 sessionEndedMessageProvider,而 build 里的 ref.listen 只捕获注册之后的
// 变化,会漏掉这个既有值 → 之前「被顶下线无提示」的根因。这里在首帧主动读一次。
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
final msg = ref.read(sessionEndedMessageProvider);
if (msg != null && msg.isNotEmpty) _showSessionEndedDialog(msg);
});
}
/// 弹出「登录已失效 / 被强制下线」提示弹窗,并清空 provider 避免重复弹。
void _showSessionEndedDialog(String msg) {
if (!mounted) return;
ref.read(sessionEndedMessageProvider.notifier).state = null;
showDialog<void>(
context: context,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.lock_outline, color: AppTheme.danger),
title: const Text('登录已失效'),
content: Text(msg),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('我知道了'),
),
],
),
);
}
Future<void> _loadHistory() async {
@@ -346,14 +374,12 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
@override
Widget build(BuildContext context) {
// 被踢下线 / 会话失效:登出后跳回登录页,弹一次提示并清空
// 已在登录页时会话失效(例如停留在登录页期间后台请求被吊销):弹窗提示。
// 进入登录页之前就置入的既有值由 initState 的首帧读取处理。
ref.listen<String?>(sessionEndedMessageProvider, (prev, next) {
if (next != null && next.isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(next), backgroundColor: AppTheme.danger));
ref.read(sessionEndedMessageProvider.notifier).state = null;
_showSessionEndedDialog(next);
});
}
});
@@ -604,24 +604,19 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
const LinearProgressIndicator(),
error: (_, __) => const Text('加载失败'),
data: (warehouses) =>
DropdownButtonFormField<int>(
value: _warehouseId,
hint: const Text('请选择仓库',
style: TextStyle(fontSize: 13)),
items: warehouses
.map((w) => DropdownMenuItem(
value: w.id,
child: Text(w.name,
style: const TextStyle(
fontSize: 13))))
SearchableOptionField(
options: warehouses
.map((w) => OptionItem(
id: w.id, name: w.name))
.toList(),
selectedId: _warehouseId,
hint: '请选择仓库',
dialogTitle: '选择入库仓库',
isRequired: true,
onChanged: (v) {
setState(() => _warehouseId = v);
if (v != null) _loadInventory(v);
},
validator: (v) =>
v == null ? '不能为空' : null,
decoration: const InputDecoration(),
),
),
),
@@ -633,22 +628,19 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
const LinearProgressIndicator(),
error: (_, __) => const Text('加载失败'),
data: (result) =>
DropdownButtonFormField<int>(
value: _partnerId,
hint: const Text('请选择供应商',
style: TextStyle(fontSize: 13)),
items: result.data
.map((p) => DropdownMenuItem(
value: p.id,
child: Text(p.name,
style: const TextStyle(
fontSize: 13))))
SearchableOptionField(
options: result.data
.map((p) => OptionItem(
id: p.id,
name: p.name,
code: p.code))
.toList(),
selectedId: _partnerId,
hint: '请选择供应商',
dialogTitle: '选择供应商',
isRequired: true,
onChanged: (v) =>
setState(() => _partnerId = v),
validator: (v) =>
v == null ? '不能为空' : null,
decoration: const InputDecoration(),
),
),
),
@@ -9,6 +9,7 @@ import '../../core/utils/print_util.dart';
import '../../core/utils/date_util.dart';
import '../../models/stock_out.dart';
import '../../widgets/date_picker_field.dart';
import '../../widgets/searchable_option_field.dart';
import '../../core/config/app_constants.dart';
import '../../providers/inventory_provider.dart';
import '../../providers/partner_provider.dart';
@@ -498,24 +499,19 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
const LinearProgressIndicator(),
error: (_, __) => const Text('加载失败'),
data: (warehouses) =>
DropdownButtonFormField<int>(
value: _warehouseId,
hint: const Text('请选择仓库',
style: TextStyle(fontSize: 13)),
items: warehouses
.map((w) => DropdownMenuItem(
value: w.id,
child: Text(w.name,
style: const TextStyle(
fontSize: 13))))
SearchableOptionField(
options: warehouses
.map((w) => OptionItem(
id: w.id, name: w.name))
.toList(),
selectedId: _warehouseId,
hint: '请选择仓库',
dialogTitle: '选择出库仓库',
isRequired: true,
onChanged: (v) {
setState(() => _warehouseId = v);
if (v != null) _loadInventory(v);
},
validator: (v) =>
v == null ? '不能为空' : null,
decoration: const InputDecoration(),
),
),
),
@@ -526,20 +522,18 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
const LinearProgressIndicator(),
error: (_, __) => const Text('加载失败'),
data: (result) =>
DropdownButtonFormField<int>(
value: _partnerId,
hint: const Text('请选择客户',
style: TextStyle(fontSize: 13)),
items: result.data
.map((p) => DropdownMenuItem(
value: p.id,
child: Text(p.name,
style: const TextStyle(
fontSize: 13))))
SearchableOptionField(
options: result.data
.map((p) => OptionItem(
id: p.id,
name: p.name,
code: p.code))
.toList(),
selectedId: _partnerId,
hint: '请选择客户',
dialogTitle: '选择客户',
onChanged: (v) =>
setState(() => _partnerId = v),
decoration: const InputDecoration(),
),
),
),