feat(client): 出入库列表状态筛选改多选并默认草稿+待审核(两端,原型同步)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-14 10:44:46 +08:00
parent e499bbc842
commit d4c5964932
26 changed files with 782 additions and 291 deletions
+2 -1
View File
@@ -38,7 +38,8 @@ final stockInSummary30Provider =
class StockInListNotifier extends AsyncNotifier<PageResult<StockInOrder>> { class StockInListNotifier extends AsyncNotifier<PageResult<StockInOrder>> {
int _page = 1; int _page = 1;
int _pageSize = AppConstants.defaultPageSize; int _pageSize = AppConstants.defaultPageSize;
String _status = ''; // 默认「草稿+待审核」(2026-07-14 用户拍板,状态筛选改多选后的默认态)。
String _status = 'draft,pending';
String? _startDate; String? _startDate;
String? _endDate; String? _endDate;
String _keyword = ''; String _keyword = '';
+2 -1
View File
@@ -38,7 +38,8 @@ final stockOutSummary30Provider =
class StockOutListNotifier extends AsyncNotifier<PageResult<StockOutOrder>> { class StockOutListNotifier extends AsyncNotifier<PageResult<StockOutOrder>> {
int _page = 1; int _page = 1;
int _pageSize = AppConstants.defaultPageSize; int _pageSize = AppConstants.defaultPageSize;
String _status = ''; // 默认「草稿+待审核」(2026-07-14 用户拍板,状态筛选改多选后的默认态)。
String _status = 'draft,pending';
String? _startDate; String? _startDate;
String? _endDate; String? _endDate;
String _keyword = ''; String _keyword = '';
@@ -17,6 +17,7 @@ import '../../models/stock_summary.dart';
import '../../providers/stock_in_provider.dart'; import '../../providers/stock_in_provider.dart';
import '../../widgets/ds/ds_atoms.dart'; import '../../widgets/ds/ds_atoms.dart';
import '../../widgets/ds/ds_kpi.dart'; import '../../widgets/ds/ds_kpi.dart';
import '../../widgets/ds/ds_menu.dart';
import '../../widgets/ds/ds_table.dart'; import '../../widgets/ds/ds_table.dart';
import '../../widgets/ds/m_kpi_grid.dart'; import '../../widgets/ds/m_kpi_grid.dart';
import '../../widgets/ds/m_search_row.dart'; import '../../widgets/ds/m_search_row.dart';
@@ -53,11 +54,13 @@ class StockInListScreen extends ConsumerStatefulWidget {
class _StockInListScreenState extends ConsumerState<StockInListScreen> { class _StockInListScreenState extends ConsumerState<StockInListScreen> {
final _searchCtrl = TextEditingController(); final _searchCtrl = TextEditingController();
String _statusFilter = ''; // '' = 全部 // 状态筛选 2026-07-14 改多选:主状态(草稿/待审核/已审核/已拒绝)多选,空集=全部
// 默认「草稿+待审核」(D3 用户拍板)。退单伪状态走 _detail['return_state'],与主状态互斥(D4)。
Set<String> _statusFilter = {'draft', 'pending'};
DateTimeRange? _dateRange; DateTimeRange? _dateRange;
String _datePresetLabel = ''; // 入库时间 chip 显示的预设名(近7天/本月/自定义…) String _datePresetLabel = ''; // 入库时间 chip 显示的预设名(近7天/本月/自定义…)
String? _warehouseName; // 仓库客户端筛选(单选) String? _warehouseName; // 仓库客户端筛选(单选)
// 详细搜索多字段(含 partner_id,与工具栏供应商联动)。key 用后端参数名。 // 详细搜索多字段(含 partner_id,与工具栏供应商联动return_state 也走这里)。key 用后端参数名。
Map<String, String> _detail = const {}; Map<String, String> _detail = const {};
// 表格列(照原型顺序,固定列,无列设置)。 // 表格列(照原型顺序,固定列,无列设置)。
@@ -73,25 +76,28 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
('actions', '操作'), ('actions', '操作'),
]; ];
static const _statusOptions = [ // 主状态(多选)编码 → 中文标签。
('', '全部'), static const _mainCodes = ['draft', 'pending', 'approved', 'rejected'];
('draft', '草稿'), static const _mainLabels = {
('pending', '待审核'), 'draft': '草稿',
('approved', '审核'), 'pending': '审核',
('rejected', '拒绝'), 'approved': '审核',
// 退单状态(return_state):与主状态互斥,走 detail 服务端过滤。 'rejected': '已拒绝',
('ret:partial', '部分退单'),
('ret:full', '已退单'),
];
// 单视图已知主状态;未知值(如旧版残留的 'pending,draft' 多值)进页面时归零。
static const _knownStatuses = {
'',
'draft',
'pending',
'approved',
'rejected'
}; };
// 退单伪状态(与主状态互斥,单选)编码 → 中文标签。
static const _retCodes = ['partial', 'full'];
static const _retLabels = {'partial': '部分退单', 'full': '已退单'};
static const _defaultStatuses = {'draft', 'pending'};
String? get _retState => _detail['return_state'];
/// 状态是否为默认态(草稿+待审核、无退单)——仅用于「重置」按钮高亮判断。
bool get _isDefaultStatus {
final rs = _retState;
return (rs == null || rs.isEmpty) &&
_statusFilter.length == 2 &&
_statusFilter.containsAll(_defaultStatuses);
}
@override @override
void initState() { void initState() {
@@ -101,15 +107,31 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
final n = ref.read(stockInListProvider.notifier); final n = ref.read(stockInListProvider.notifier);
if (!_knownStatuses.contains(n.currentStatus)) { // 逗号拆分逐值校验合法性,非法(或全部非法)才归默认;'' 本身是合法的「全部」态,
n.setStatus(''); // 归零无法在单视图呈现的残留状态 // 不可等同于非法一律重置——否则默认值 'draft,pending' 刚设就被清掉。
final raw = n.currentStatus;
if (raw.isEmpty) {
_statusFilter = {};
} else { } else {
_statusFilter = n.currentStatus; final parts = raw
.split(',')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toSet();
final valid = parts.where(_mainCodes.contains).toSet();
if (valid.isEmpty) {
_statusFilter = Set.of(_defaultStatuses);
n.setStatus(_statusFilter.join(','));
} else {
_statusFilter = valid;
}
} }
_searchCtrl.text = n.currentKeyword; _searchCtrl.text = n.currentKeyword;
_detail = Map<String, String>.of(n.currentDetail); _detail = Map<String, String>.of(n.currentDetail);
final rs = _detail['return_state']; final rs = _detail['return_state'];
if (rs != null && rs.isNotEmpty) _statusFilter = 'ret:$rs'; if (rs != null && rs.isNotEmpty) {
_statusFilter = {}; // 退单态与主状态互斥,主状态显示清空
}
setState(() {}); setState(() {});
}); });
} }
@@ -132,12 +154,24 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
? '${_dateRange!.end.year}-${_dateRange!.end.month.toString().padLeft(2, '0')}-${_dateRange!.end.day.toString().padLeft(2, '0')}' ? '${_dateRange!.end.year}-${_dateRange!.end.month.toString().padLeft(2, '0')}-${_dateRange!.end.day.toString().padLeft(2, '0')}'
: null; : null;
/// 状态摘要:无=全部、1 项=值本身、多项=「N 项」;退单态优先展示(互斥)。
String get _statusLabelOf { String get _statusLabelOf {
return _statusOptions final rs = _retState;
.where((o) => o.$1 == _statusFilter) if (rs != null && rs.isNotEmpty) return _retLabels[rs] ?? '全部';
.map((o) => o.$2) if (_statusFilter.isEmpty) return '全部';
.firstOrNull ?? if (_statusFilter.length == 1) {
'全部'; return _mainLabels[_statusFilter.first] ?? '全部';
}
return '${_statusFilter.length}';
}
/// chip 摘要值(null=不显示,即「全部」态)。
String? get _statusChipValue {
final rs = _retState;
if (rs != null && rs.isNotEmpty) return _retLabels[rs];
if (_statusFilter.isEmpty) return null;
if (_statusFilter.length == 1) return _mainLabels[_statusFilter.first];
return '${_statusFilter.length}';
} }
OrderStatus _apiStatusToEnum(String status) { OrderStatus _apiStatusToEnum(String status) {
@@ -153,24 +187,88 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
} }
} }
/// 覆盖式设置单一状态(D5:KPI 卡点击覆盖为该单值,不是 toggle 集合成员);
/// `s` 为空表示「全部」,`ret:` 前缀表示退单伪状态(与主状态互斥)。
void _setStatus(String s) { void _setStatus(String s) {
setState(() => _statusFilter = s);
final n = ref.read(stockInListProvider.notifier); final n = ref.read(stockInListProvider.notifier);
final d = Map<String, String>.of(_detail); final d = Map<String, String>.of(_detail);
if (s.startsWith('ret:')) { if (s.startsWith('ret:')) {
// 退单状态:走 return_state 服务端过滤,主状态清空(二者互斥)。
d['return_state'] = s.substring(4); d['return_state'] = s.substring(4);
_detail = d; setState(() {
_statusFilter = {};
_detail = d;
});
n.setStatus(''); n.setStatus('');
n.setDetail(d); n.setDetail(d);
} else { } else {
d.remove('return_state'); d.remove('return_state');
_detail = d; setState(() {
n.setStatus(s); _statusFilter = s.isEmpty ? {} : {s};
_detail = d;
});
n.setStatus(_statusFilter.join(','));
n.setDetail(d); n.setDetail(d);
} }
} }
/// 状态默认态(草稿+待审核)——chip「×」/「重置」用(D3:回默认,不是回「全部」)。
void _setStatusDefault() {
final d = Map<String, String>.of(_detail)..remove('return_state');
setState(() {
_statusFilter = Set.of(_defaultStatuses);
_detail = d;
});
final n = ref.read(stockInListProvider.notifier);
n.setStatus(_statusFilter.join(','));
n.setDetail(d);
}
/// 状态多选下拉:主状态 checkbox 多选 + 退单伪状态互斥(选其一清另一侧)。
void _openStatusMenu(BuildContext anchorContext) {
showDsMultiMenu<String>(
anchorContext,
itemsBuilder: () => [
for (final c in _mainCodes)
DsMenuItem(
value: c,
label: _mainLabels[c]!,
selected: _statusFilter.contains(c)),
for (final r in _retCodes)
DsMenuItem(
value: 'ret:$r',
label: _retLabels[r]!,
selected: _retState == r),
],
onToggle: (v) {
if (v.startsWith('ret:')) {
final code = v.substring(4);
final turningOn = _retState != code;
final d = Map<String, String>.of(_detail);
if (turningOn) {
d['return_state'] = code;
} else {
d.remove('return_state');
}
setState(() {
_statusFilter = {};
_detail = d;
});
} else {
final next = Set.of(_statusFilter);
next.contains(v) ? next.remove(v) : next.add(v);
final d = Map<String, String>.of(_detail)..remove('return_state');
setState(() {
_statusFilter = next;
_detail = d;
});
}
final n = ref.read(stockInListProvider.notifier);
n.setStatus(_statusFilter.join(','));
n.setDetail(_detail);
},
);
}
void _setDetailPartner(int? id) { void _setDetailPartner(int? id) {
final d = Map<String, String>.of(_detail); final d = Map<String, String>.of(_detail);
if (id == null) { if (id == null) {
@@ -185,7 +283,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
void _resetFilters() { void _resetFilters() {
setState(() { setState(() {
_searchCtrl.clear(); _searchCtrl.clear();
_statusFilter = ''; _statusFilter = Set.of(_defaultStatuses); // D3:回默认「草稿+待审核」,不是回「全部」
_dateRange = null; _dateRange = null;
_datePresetLabel = ''; _datePresetLabel = '';
_warehouseName = null; _warehouseName = null;
@@ -193,7 +291,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
}); });
final n = ref.read(stockInListProvider.notifier); final n = ref.read(stockInListProvider.notifier);
n.setKeyword(''); n.setKeyword('');
n.setStatus(''); n.setStatus(_statusFilter.join(','));
n.setDateRange(null, null); n.setDateRange(null, null);
n.setDetail(const {}); n.setDetail(const {});
} }
@@ -270,6 +368,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
), ),
); );
/// 详细搜索的「状态」字段仍单选(未扩展为多选):多选态无法单值呈现时归「全部」。
String get _statusForAdvDialog =>
_statusFilter.length == 1 ? _statusFilter.first : '';
Future<void> _openAdvSearch() async { Future<void> _openAdvSearch() async {
final _AdvResult? res; final _AdvResult? res;
if (context.isMobile) { if (context.isMobile) {
@@ -281,7 +383,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
builder: (_) => _AdvSearchSheet( builder: (_) => _AdvSearchSheet(
key: formKey, key: formKey,
initialDetail: _detail, initialDetail: _detail,
initialStatus: _statusFilter, initialStatus: _statusForAdvDialog,
initialRange: _dateRange, initialRange: _dateRange,
), ),
actions: [ actions: [
@@ -296,7 +398,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
context: context, context: context,
builder: (_) => _AdvSearchDialog( builder: (_) => _AdvSearchDialog(
initialDetail: _detail, initialDetail: _detail,
initialStatus: _statusFilter, initialStatus: _statusForAdvDialog,
initialRange: _dateRange, initialRange: _dateRange,
), ),
); );
@@ -305,19 +407,20 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
if (r == null || !mounted) return; if (r == null || !mounted) return;
setState(() { setState(() {
_detail = r.detail; _detail = r.detail;
_statusFilter = r.status; _statusFilter = r.status.isEmpty ? {} : {r.status};
_dateRange = r.dateRange; _dateRange = r.dateRange;
}); });
final n = ref.read(stockInListProvider.notifier); final n = ref.read(stockInListProvider.notifier);
n.setStatus(r.status); n.setStatus(_statusFilter.join(','));
n.setDateRange(_startDate, _endDate); n.setDateRange(_startDate, _endDate);
n.setDetail(r.detail); n.setDetail(r.detail);
} }
int get _advCount { int get _advCount {
var c = _detail.entries.where((e) => e.value.trim().isNotEmpty).length; var c = _detail.entries.where((e) => e.value.trim().isNotEmpty).length;
// 主状态也算一项(退单状态已作为 _detail.return_state 计入,故排除 ret:)。 // 主状态非默认态才算一项(默认「草稿+待审核」不计入,避免刚进屏就显示已筛选;
if (_statusFilter.isNotEmpty && !_statusFilter.startsWith('ret:')) c++; // 退单状态已作为 _detail.return_state 计入,此时 _statusFilter 为空不会重复加)。
if (_statusFilter.isNotEmpty && !_isDefaultStatus) c++;
return c; return c;
} }
@@ -620,16 +723,20 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
delta: pendingCount > 0 ? '需尽快处理' : '点击筛选', delta: pendingCount > 0 ? '需尽快处理' : '点击筛选',
deltaTone: deltaTone:
pendingCount > 0 ? MKpiDeltaTone.warn : MKpiDeltaTone.normal, pendingCount > 0 ? MKpiDeltaTone.warn : MKpiDeltaTone.normal,
selected: _statusFilter == 'pending', // D5:KPI 卡点击覆盖为该单值(非 toggle 集合成员)。
onTap: () => selected: _retState == null &&
_setStatus(_statusFilter == 'pending' ? '' : 'pending')), _statusFilter.length == 1 &&
_statusFilter.contains('pending'),
onTap: () => _setStatus('pending')),
MKpiItem( MKpiItem(
label: '草稿 · 点击筛选', label: '草稿 · 点击筛选',
value: '$draftCount', value: '$draftCount',
icon: LucideIcons.fileText, icon: LucideIcons.fileText,
delta: draftCount > 0 ? '待提交审核' : '点击筛选', delta: draftCount > 0 ? '待提交审核' : '点击筛选',
selected: _statusFilter == 'draft', selected: _retState == null &&
onTap: () => _setStatus(_statusFilter == 'draft' ? '' : 'draft')), _statusFilter.length == 1 &&
_statusFilter.contains('draft'),
onTap: () => _setStatus('draft')),
]), ]),
); );
} }
@@ -652,7 +759,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
onSubmitted: (v) => onSubmitted: (v) =>
ref.read(stockInListProvider.notifier).setKeyword(v), ref.read(stockInListProvider.notifier).setKeyword(v),
statusLabel: _statusLabelOf, statusLabel: _statusLabelOf,
statusActive: _statusFilter.isNotEmpty, statusActive: _statusFilter.isNotEmpty || (_retState?.isNotEmpty ?? false),
onStatusTap: _openStatusSheet, onStatusTap: _openStatusSheet,
filterActive: _mobileAdvActive, filterActive: _mobileAdvActive,
onFilterTap: _openAdvSearch, onFilterTap: _openAdvSearch,
@@ -676,53 +783,39 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
); );
} }
/// 状态筛选底部 sheet原型 openStatusSheet 的 m-opt 列表:图标 + 状态词 + 勾) /// 状态筛选底部 sheet多选勾选 + 确定应用,D5/D4):主状态多选 + 退单伪状态互斥
Future<void> _openStatusSheet() async { Future<void> _openStatusSheet() async {
final sel = await showMSheet<String>( final formKey = GlobalKey<_StatusMultiSheetState>();
final rsInit = _retState;
final res = await showMSheet<_StatusSheetResult>(
context, context,
title: '单据状态', title: '单据状态',
builder: (ctx) { builder: (_) => _StatusMultiSheet(
final t = ctx.tokens; key: formKey,
return Column( initialStatuses: _statusFilter,
mainAxisSize: MainAxisSize.min, initialRetState: (rsInit != null && rsInit.isNotEmpty) ? rsInit : null,
children: [ ),
for (var i = 0; i < _statusOptions.length; i++) actions: [
InkWell( DsButton('重置', onPressed: () => formKey.currentState?.reset()),
onTap: () => Navigator.of(ctx).pop(_statusOptions[i].$1), DsButton('确定',
child: Container( variant: DsBtnVariant.primary,
padding: onPressed: () => formKey.currentState?.apply()),
const EdgeInsets.symmetric(vertical: 13, horizontal: 4), ],
decoration: BoxDecoration(
border: i < _statusOptions.length - 1
? Border(bottom: BorderSide(color: t.borderSubtle))
: null,
),
child: Row(children: [
if (_statusOptions[i].$2 != '全部') ...[
DsIconBadge(_statusOptions[i].$2),
const SizedBox(width: 10),
],
Text(_statusOptions[i].$2,
style: TextStyle(
fontSize: AppDims.fsBody,
fontWeight: _statusOptions[i].$1 == _statusFilter
? FontWeight.w600
: FontWeight.w400,
color: _statusOptions[i].$1 == _statusFilter
? t.primary
: t.text)),
const Spacer(),
if (_statusOptions[i].$1 == _statusFilter)
Icon(LucideIcons.check, size: 18, color: t.primary),
]),
),
),
],
);
},
); );
if (sel == null || !mounted) return; if (res == null || !mounted) return;
_setStatus(sel); final d = Map<String, String>.of(_detail);
if (res.retState != null) {
d['return_state'] = res.retState!;
} else {
d.remove('return_state');
}
setState(() {
_statusFilter = res.statuses;
_detail = d;
});
final n = ref.read(stockInListProvider.notifier);
n.setStatus(_statusFilter.join(','));
n.setDetail(d);
} }
// ── 工具栏(原型 .toolbar)───────────────────────────────────────── // ── 工具栏(原型 .toolbar)─────────────────────────────────────────
@@ -772,23 +865,15 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
onClear: () => setState(() => _warehouseName = null)), onClear: () => setState(() => _warehouseName = null)),
); );
final statusChip = PopupMenuButton<String>( // 状态:多选 chip(草稿/待审核/已审核/已拒绝 checkbox 多选 + 部分退单/已退单 互斥单选),
onSelected: _setStatus, // 摘要「值/N 项」,× 单清回默认「草稿+待审核」(D3/D4,照 inventory_list_screen 多选 chip 手法)。
offset: const Offset(0, 40), final statusChip = Builder(
color: ctx.tokens.surface, builder: (chipCtx) => DsChip(
elevation: 8, label: '状态',
shape: RoundedRectangleBorder( value: _statusChipValue,
side: BorderSide(color: ctx.tokens.border), onTap: () => _openStatusMenu(chipCtx),
borderRadius: BorderRadius.circular(AppDims.rMd), onClear: _setStatusDefault,
), ),
itemBuilder: (_) => _statusOptions
.map((s) =>
PopupMenuItem(value: s.$1, height: 38, child: Text(s.$2)))
.toList(),
child: DsChip(
label: '状态',
value: _statusFilter.isEmpty ? null : _statusLabelOf,
onClear: () => _setStatus('')),
); );
final dateChip = _dateMenu(ctx); final dateChip = _dateMenu(ctx);
@@ -1478,6 +1563,102 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
} }
} }
// ── 状态筛选底部 sheet(原型 openStatusSheet 多选版):主状态 checkbox 多选 +
// 退单伪状态互斥单选,底部「重置/确定」应用(对齐 D3/D4/Step5)─────────────
class _StatusSheetResult {
final Set<String> statuses;
final String? retState; // null=未选退单态
const _StatusSheetResult(this.statuses, this.retState);
}
class _StatusMultiSheet extends StatefulWidget {
final Set<String> initialStatuses;
final String? initialRetState;
const _StatusMultiSheet({
super.key,
required this.initialStatuses,
required this.initialRetState,
});
@override
State<_StatusMultiSheet> createState() => _StatusMultiSheetState();
}
class _StatusMultiSheetState extends State<_StatusMultiSheet> {
late Set<String> _sel;
String? _ret;
@override
void initState() {
super.initState();
_sel = Set.of(widget.initialStatuses);
_ret = widget.initialRetState;
}
/// 重置(sheet actions「重置」调用):回默认「草稿+待审核」(D3)。
void reset() {
setState(() {
_sel = Set.of(_StockInListScreenState._defaultStatuses);
_ret = null;
});
}
/// 确定(sheet actions「确定」调用):组装结果并关闭 sheet。
void apply() {
Navigator.of(context).pop(_StatusSheetResult(_sel, _ret));
}
@override
Widget build(BuildContext context) {
final t = context.tokens;
final mainCodes = _StockInListScreenState._mainCodes;
final mainLabels = _StockInListScreenState._mainLabels;
final retCodes = _StockInListScreenState._retCodes;
final retLabels = _StockInListScreenState._retLabels;
Widget row(String code, String label, {required bool isRet}) {
final sel = isRet ? _ret == code : _sel.contains(code);
return InkWell(
onTap: () => setState(() {
if (isRet) {
_ret = _ret == code ? null : code;
_sel = {};
} else {
final next = Set.of(_sel);
next.contains(code) ? next.remove(code) : next.add(code);
_sel = next;
_ret = null;
}
}),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 4),
decoration:
BoxDecoration(border: Border(bottom: BorderSide(color: t.borderSubtle))),
child: Row(children: [
DsIconBadge(label),
const SizedBox(width: 10),
Text(label,
style: TextStyle(
fontSize: AppDims.fsBody,
fontWeight: sel ? FontWeight.w600 : FontWeight.w400,
color: sel ? t.primary : t.text)),
const Spacer(),
if (sel) Icon(LucideIcons.check, size: 18, color: t.primary),
]),
),
);
}
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final c in mainCodes) row(c, mainLabels[c]!, isRet: false),
for (final r in retCodes) row(r, retLabels[r]!, isRet: true),
],
);
}
}
// ── 详细搜索按钮(原型 .btn.soft,浅蓝突出;有条件时转 primary)──────────── // ── 详细搜索按钮(原型 .btn.soft,浅蓝突出;有条件时转 primary)────────────
class _AdvSearchButton extends StatelessWidget { class _AdvSearchButton extends StatelessWidget {
final int count; final int count;
@@ -31,6 +31,7 @@ import '../../widgets/label_preview_dialog.dart';
import '../../widgets/order_detail_drawer.dart'; import '../../widgets/order_detail_drawer.dart';
import '../../widgets/ds/ds_atoms.dart'; import '../../widgets/ds/ds_atoms.dart';
import '../../widgets/ds/ds_kpi.dart'; import '../../widgets/ds/ds_kpi.dart';
import '../../widgets/ds/ds_menu.dart';
import '../../widgets/ds/ds_table.dart'; import '../../widgets/ds/ds_table.dart';
import '../../widgets/ds/m_kpi_grid.dart'; import '../../widgets/ds/m_kpi_grid.dart';
import '../../widgets/ds/m_search_row.dart'; import '../../widgets/ds/m_search_row.dart';
@@ -59,8 +60,9 @@ class StockOutListScreen extends ConsumerStatefulWidget {
class _StockOutListScreenState extends ConsumerState<StockOutListScreen> { class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
final _searchCtrl = TextEditingController(); final _searchCtrl = TextEditingController();
// 状态'' = 全部)+ 日期区间:走 setStatus / setDateRange。 // 状态筛选 2026-07-14 改多选:主状态(草稿/待审核/已审核/已拒绝)多选,空集=全部;
String _statusFilter = ''; // 默认「草稿+待审核」(D3 用户拍板)。退单伪状态仍走 _returnState,与主状态互斥(D4)。
Set<String> _statusFilter = {'draft', 'pending'};
DateTimeRange? _dateRange; DateTimeRange? _dateRange;
String _appliedDateKey = ''; String _appliedDateKey = '';
String _datePresetLabel = ''; // 出库时间 chip 显示的预设名 String _datePresetLabel = ''; // 出库时间 chip 显示的预设名
@@ -80,23 +82,24 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
int? _reviewerId; int? _reviewerId;
String _returnState = ''; // '' / partial / full(退单状态,走 detail 服务端过滤) String _returnState = ''; // '' / partial / full(退单状态,走 detail 服务端过滤)
static const _knownStatuses = { // 主状态(多选)编码 → 中文标签。
'', static const _mainCodes = ['draft', 'pending', 'approved', 'rejected'];
'draft', static const _mainLabels = {
'pending', 'draft': '草稿',
'approved', 'pending': '待审核',
'rejected' 'approved': '已审核',
'rejected': '已拒绝',
}; };
static const _statusOptions = <(String, String)>[ // 退单伪状态(与主状态互斥,单选)编码 → 中文标签。
('', '全部'), static const _retCodes = ['partial', 'full'];
('draft', '草稿'), static const _retLabels = {'partial': '部分退单', 'full': '已退单'};
('pending', '待审核'), static const _defaultStatuses = {'draft', 'pending'};
('approved', '已审核'),
('rejected', '已拒绝'), /// 状态是否为默认态(草稿+待审核、无退单)——仅用于「重置」按钮高亮判断。
// 退单状态(return_state):与主状态互斥。 bool get _isDefaultStatus =>
('ret:partial', '部分退单'), _returnState.isEmpty &&
('ret:full', '已退单'), _statusFilter.length == 2 &&
]; _statusFilter.containsAll(_defaultStatuses);
@override @override
void initState() { void initState() {
@@ -106,10 +109,24 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
final n = ref.read(stockOutListProvider.notifier); final n = ref.read(stockOutListProvider.notifier);
if (!_knownStatuses.contains(n.currentStatus)) { // 逗号拆分逐值校验合法性,非法(或全部非法)才归默认;'' 本身是合法的「全部」态,
n.setStatus(''); // 不可等同于非法一律重置——否则默认值 'draft,pending' 刚设就被清掉。
final raw = n.currentStatus;
if (raw.isEmpty) {
_statusFilter = {};
} else { } else {
_statusFilter = n.currentStatus; final parts = raw
.split(',')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toSet();
final valid = parts.where(_mainCodes.contains).toSet();
if (valid.isEmpty) {
_statusFilter = Set.of(_defaultStatuses);
n.setStatus(_statusFilter.join(','));
} else {
_statusFilter = valid;
}
} }
_searchCtrl.text = n.currentKeyword; _searchCtrl.text = n.currentKeyword;
final d = n.currentDetail; final d = n.currentDetail;
@@ -123,7 +140,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
_operatorId = int.tryParse(d['operator_id'] ?? ''); _operatorId = int.tryParse(d['operator_id'] ?? '');
_reviewerId = int.tryParse(d['reviewer_id'] ?? ''); _reviewerId = int.tryParse(d['reviewer_id'] ?? '');
_returnState = d['return_state'] ?? ''; _returnState = d['return_state'] ?? '';
if (_returnState.isNotEmpty) _statusFilter = 'ret:$_returnState'; if (_returnState.isNotEmpty) _statusFilter = {}; // 退单态与主状态互斥
setState(() {}); setState(() {});
}); });
} }
@@ -162,18 +179,19 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
if (_spec.isNotEmpty) c++; if (_spec.isNotEmpty) c++;
if (_operatorId != null) c++; if (_operatorId != null) c++;
if (_reviewerId != null) c++; if (_reviewerId != null) c++;
// 状态也是详细搜索里的一项(含退单状态)——之前漏算导致徽标少 1。 // 状态非默认态才算一项(默认「草稿+待审核」不计入,避免刚进屏就显示已筛选;
if (_statusFilter.isNotEmpty) c++; // 含退单状态:选中退单态时 _statusFilter 为空,走下面单独判断)。
if (_statusFilter.isNotEmpty && !_isDefaultStatus) c++;
if (_returnState.isNotEmpty) c++;
return c; return c;
} }
bool get _hasAnyFilter => bool get _hasAnyFilter =>
_searchCtrl.text.isNotEmpty || _searchCtrl.text.isNotEmpty ||
_statusFilter.isNotEmpty || !_isDefaultStatus ||
_dateRange != null || _dateRange != null ||
_filterWarehouseName != null || _filterWarehouseName != null ||
_partnerId != null || _partnerId != null ||
_returnState.isNotEmpty ||
_advCount > 0; _advCount > 0;
String? get _startDate => _dateRange != null String? get _startDate => _dateRange != null
@@ -194,7 +212,8 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
final n = ref.read(stockOutListProvider.notifier); final n = ref.read(stockOutListProvider.notifier);
final detail = _buildDetail(); final detail = _buildDetail();
if (!mapEquals(n.currentDetail, detail)) n.setDetail(detail); if (!mapEquals(n.currentDetail, detail)) n.setDetail(detail);
if (n.currentStatus != _statusFilter) n.setStatus(_statusFilter); final statusStr = _statusFilter.join(',');
if (n.currentStatus != statusStr) n.setStatus(statusStr);
final key = '${_startDate ?? ''}~${_endDate ?? ''}'; final key = '${_startDate ?? ''}~${_endDate ?? ''}';
if (key != _appliedDateKey) { if (key != _appliedDateKey) {
_appliedDateKey = key; _appliedDateKey = key;
@@ -205,7 +224,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
void _resetFilters() { void _resetFilters() {
setState(() { setState(() {
_searchCtrl.clear(); _searchCtrl.clear();
_statusFilter = ''; _statusFilter = Set.of(_defaultStatuses); // D3:回默认「草稿+待审核」,不是回「全部」
_dateRange = null; _dateRange = null;
_appliedDateKey = ''; _appliedDateKey = '';
_datePresetLabel = ''; _datePresetLabel = '';
@@ -223,7 +242,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
}); });
final n = ref.read(stockOutListProvider.notifier); final n = ref.read(stockOutListProvider.notifier);
n.setKeyword(''); n.setKeyword('');
n.setStatus(''); n.setStatus(_statusFilter.join(','));
n.setDateRange(null, null); n.setDateRange(null, null);
n.setDetail(const {}); n.setDetail(const {});
} }
@@ -545,10 +564,8 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
icon: LucideIcons.clock, icon: LucideIcons.clock,
tone: DsKpiTone.alert, tone: DsKpiTone.alert,
delta: (summary?.pendingCount ?? 0) > 0 ? '需尽快处理' : '点击筛选', delta: (summary?.pendingCount ?? 0) > 0 ? '需尽快处理' : '点击筛选',
onTap: () { // D5:KPI 卡点击覆盖为该单值(非 toggle 集合成员)。
setState(() => _statusFilter = 'pending'); onTap: () => _setStatus('pending'),
ref.read(stockOutListProvider.notifier).setStatus('pending');
},
), ),
DsKpi( DsKpi(
title: '草稿单数 · 点击筛选', title: '草稿单数 · 点击筛选',
@@ -556,10 +573,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
icon: LucideIcons.filePen, icon: LucideIcons.filePen,
tone: DsKpiTone.warn, // 暗黄(用户拍板) tone: DsKpiTone.warn, // 暗黄(用户拍板)
delta: (summary?.draftCount ?? 0) > 0 ? '待提交审核' : '点击筛选', delta: (summary?.draftCount ?? 0) > 0 ? '待提交审核' : '点击筛选',
onTap: () { onTap: () => _setStatus('draft'),
setState(() => _statusFilter = 'draft');
ref.read(stockOutListProvider.notifier).setStatus('draft');
},
), ),
]; ];
@@ -591,26 +605,76 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
// ── 窄屏形态(对齐移动原型 m-stock-out-list.html)───────────────── // ── 窄屏形态(对齐移动原型 m-stock-out-list.html)─────────────────
/// 状态设置(主状态 / ret:退单态互斥),KPI 卡与状态 sheet 共用。 /// 覆盖式设置单一状态(D5:KPI 卡点击覆盖为该单值,不是 toggle 集合成员);
/// `code` 为空表示「全部」,`ret:` 前缀表示退单伪状态(与主状态互斥)。
void _setStatus(String code) { void _setStatus(String code) {
final n = ref.read(stockOutListProvider.notifier); final n = ref.read(stockOutListProvider.notifier);
if (code.startsWith('ret:')) { if (code.startsWith('ret:')) {
setState(() { setState(() {
_statusFilter = code; _statusFilter = {};
_returnState = code.substring(4); _returnState = code.substring(4);
}); });
n.setStatus(''); n.setStatus('');
_applyDetail(); _applyDetail();
} else { } else {
setState(() { setState(() {
_statusFilter = code; _statusFilter = code.isEmpty ? {} : {code};
_returnState = ''; _returnState = '';
}); });
n.setStatus(code); n.setStatus(_statusFilter.join(','));
_applyDetail(); _applyDetail();
} }
} }
/// 状态默认态(草稿+待审核)——chip「×」/「重置」用(D3:回默认,不是回「全部」)。
void _setStatusDefault() {
setState(() {
_statusFilter = Set.of(_defaultStatuses);
_returnState = '';
});
ref.read(stockOutListProvider.notifier).setStatus(_statusFilter.join(','));
_applyDetail();
}
/// 状态多选下拉:主状态 checkbox 多选 + 退单伪状态互斥(选其一清另一侧)。
void _openStatusMenu(BuildContext anchorContext) {
showDsMultiMenu<String>(
anchorContext,
itemsBuilder: () => [
for (final c in _mainCodes)
DsMenuItem(
value: c,
label: _mainLabels[c]!,
selected: _statusFilter.contains(c)),
for (final r in _retCodes)
DsMenuItem(
value: 'ret:$r',
label: _retLabels[r]!,
selected: _returnState == r),
],
onToggle: (v) {
if (v.startsWith('ret:')) {
final code = v.substring(4);
setState(() {
_returnState = _returnState == code ? '' : code;
_statusFilter = {};
});
} else {
setState(() {
final next = Set.of(_statusFilter);
next.contains(v) ? next.remove(v) : next.add(v);
_statusFilter = next;
_returnState = '';
});
}
ref
.read(stockOutListProvider.notifier)
.setStatus(_statusFilter.join(','));
_applyDetail();
},
);
}
/// 状态词 → (前景, 软底):镜像原型 .badge.b-*,全走 token。 /// 状态词 → (前景, 软底):镜像原型 .badge.b-*,全走 token。
/// 原型 .badge.ico:纯图标小徽章(卡片右上态标 / 状态 sheet 选项行)。 /// 原型 .badge.ico:纯图标小徽章(卡片右上态标 / 状态 sheet 选项行)。
/// 原型 .m-kpi 2×2:近30天笔数/金额 + 可点筛选的待审核/草稿(再点取消)。 /// 原型 .m-kpi 2×2:近30天笔数/金额 + 可点筛选的待审核/草稿(再点取消)。
@@ -648,16 +712,20 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
delta: pendingCount > 0 ? '需尽快处理' : '点击筛选', delta: pendingCount > 0 ? '需尽快处理' : '点击筛选',
deltaTone: deltaTone:
pendingCount > 0 ? MKpiDeltaTone.warn : MKpiDeltaTone.normal, pendingCount > 0 ? MKpiDeltaTone.warn : MKpiDeltaTone.normal,
selected: _statusFilter == 'pending', // D5:KPI 卡点击覆盖为该单值(非 toggle 集合成员)。
onTap: () => selected: _returnState.isEmpty &&
_setStatus(_statusFilter == 'pending' ? '' : 'pending')), _statusFilter.length == 1 &&
_statusFilter.contains('pending'),
onTap: () => _setStatus('pending')),
MKpiItem( MKpiItem(
label: '草稿 · 点击筛选', label: '草稿 · 点击筛选',
value: '$draftCount', value: '$draftCount',
icon: LucideIcons.fileText, icon: LucideIcons.fileText,
delta: draftCount > 0 ? '待提交审核' : '点击筛选', delta: draftCount > 0 ? '待提交审核' : '点击筛选',
selected: _statusFilter == 'draft', selected: _returnState.isEmpty &&
onTap: () => _setStatus(_statusFilter == 'draft' ? '' : 'draft')), _statusFilter.length == 1 &&
_statusFilter.contains('draft'),
onTap: () => _setStatus('draft')),
]), ]),
); );
} }
@@ -675,13 +743,26 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
_reviewerId != null || _reviewerId != null ||
_dateRange != null; _dateRange != null;
/// 状态摘要:无=全部、1 项=值本身、多项=「N 项」;退单态优先展示(互斥)。
String get _statusLabelOf {
if (_returnState.isNotEmpty) return _retLabels[_returnState] ?? '全部';
if (_statusFilter.isEmpty) return '全部';
if (_statusFilter.length == 1) {
return _mainLabels[_statusFilter.first] ?? '全部';
}
return '${_statusFilter.length}';
}
/// chip 摘要值(null=不显示,即「全部」态)。
String? get _statusChipValue {
if (_returnState.isNotEmpty) return _retLabels[_returnState];
if (_statusFilter.isEmpty) return null;
if (_statusFilter.length == 1) return _mainLabels[_statusFilter.first];
return '${_statusFilter.length}';
}
/// 原型搜索区:搜索框 + 纯文字状态钮 + 图标详搜钮。 /// 原型搜索区:搜索框 + 纯文字状态钮 + 图标详搜钮。
Widget _buildMobileSearchRow() { Widget _buildMobileSearchRow() {
final statusLabel = _statusFilter.isEmpty
? '全部'
: _statusOptions
.firstWhere((e) => e.$1 == _statusFilter, orElse: () => ('', '全部'))
.$2;
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 12, 0), padding: const EdgeInsets.fromLTRB(12, 10, 12, 0),
child: MSearchRow( child: MSearchRow(
@@ -691,8 +772,8 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
onChanged: (v) => ref.read(stockOutListProvider.notifier).setKeyword(v), onChanged: (v) => ref.read(stockOutListProvider.notifier).setKeyword(v),
onSubmitted: (v) => onSubmitted: (v) =>
ref.read(stockOutListProvider.notifier).setKeyword(v), ref.read(stockOutListProvider.notifier).setKeyword(v),
statusLabel: statusLabel, statusLabel: _statusLabelOf,
statusActive: _statusFilter.isNotEmpty, statusActive: _statusFilter.isNotEmpty || _returnState.isNotEmpty,
onStatusTap: _openStatusSheet, onStatusTap: _openStatusSheet,
filterActive: _mobileAdvActive, filterActive: _mobileAdvActive,
onFilterTap: _openAdvSearch, onFilterTap: _openAdvSearch,
@@ -716,53 +797,31 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
); );
} }
/// 状态筛选底部 sheet原型 openStatusSheet 的 m-opt 列表:图标 + 状态词 + 勾) /// 状态筛选底部 sheet多选勾选 + 确定应用,D5/D4):主状态多选 + 退单伪状态互斥
Future<void> _openStatusSheet() async { Future<void> _openStatusSheet() async {
final sel = await showMSheet<String>( final formKey = GlobalKey<_StatusMultiSheetState>();
final res = await showMSheet<_StatusSheetResult>(
context, context,
title: '单据状态', title: '单据状态',
builder: (ctx) { builder: (_) => _StatusMultiSheet(
final t = ctx.tokens; key: formKey,
return Column( initialStatuses: _statusFilter,
mainAxisSize: MainAxisSize.min, initialRetState: _returnState.isEmpty ? null : _returnState,
children: [ ),
for (var i = 0; i < _statusOptions.length; i++) actions: [
InkWell( DsButton('重置', onPressed: () => formKey.currentState?.reset()),
onTap: () => Navigator.of(ctx).pop(_statusOptions[i].$1), DsButton('确定',
child: Container( variant: DsBtnVariant.primary,
padding: onPressed: () => formKey.currentState?.apply()),
const EdgeInsets.symmetric(vertical: 13, horizontal: 4), ],
decoration: BoxDecoration(
border: i < _statusOptions.length - 1
? Border(bottom: BorderSide(color: t.borderSubtle))
: null,
),
child: Row(children: [
if (_statusOptions[i].$2 != '全部') ...[
DsIconBadge(_statusOptions[i].$2),
const SizedBox(width: 10),
],
Text(_statusOptions[i].$2,
style: TextStyle(
fontSize: AppDims.fsBody,
fontWeight: _statusOptions[i].$1 == _statusFilter
? FontWeight.w600
: FontWeight.w400,
color: _statusOptions[i].$1 == _statusFilter
? t.primary
: t.text)),
const Spacer(),
if (_statusOptions[i].$1 == _statusFilter)
Icon(LucideIcons.check, size: 18, color: t.primary),
]),
),
),
],
);
},
); );
if (sel == null || !mounted) return; if (res == null || !mounted) return;
_setStatus(sel); setState(() {
_statusFilter = res.statuses;
_returnState = res.retState ?? '';
});
ref.read(stockOutListProvider.notifier).setStatus(_statusFilter.join(','));
_applyDetail();
} }
// ── 工具栏(原型 .toolbar)───────────────────────────────────── // ── 工具栏(原型 .toolbar)─────────────────────────────────────
@@ -811,42 +870,15 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
onClear: () => setState(() => _filterWarehouseName = null), onClear: () => setState(() => _filterWarehouseName = null),
); );
// 状态:DsChip + 菜单。 // 状态:多选 chip(草稿/待审核/已审核/已拒绝 checkbox 多选 + 部分退单/已退单 互斥单选),
final statusLabel = _statusOptions // 摘要「值/N 项」,× 单清回默认「草稿+待审核」(D3/D4,照 inventory_list_screen 多选 chip 手法)。
.firstWhere((e) => e.$1 == _statusFilter, orElse: () => ('', '')) final statusChip = Builder(
.$2; builder: (chipCtx) => DsChip(
final statusChip = _MenuChip( label: '状态',
label: '状态', value: _statusChipValue,
value: _statusFilter.isEmpty ? null : statusLabel, onTap: () => _openStatusMenu(chipCtx),
options: _statusOptions.map((e) => e.$2).toList(), onClear: _setStatusDefault,
onSelected: (label) { ),
final code = _statusOptions.firstWhere((e) => e.$2 == label).$1;
final n = ref.read(stockOutListProvider.notifier);
if (code.startsWith('ret:')) {
// 退单状态:走 return_state,主状态清空(互斥)。
setState(() {
_statusFilter = code;
_returnState = code.substring(4);
});
n.setStatus('');
_applyDetail();
} else {
setState(() {
_statusFilter = code;
_returnState = '';
});
n.setStatus(code);
_applyDetail();
}
},
onClear: () {
setState(() {
_statusFilter = '';
_returnState = '';
});
ref.read(stockOutListProvider.notifier).setStatus('');
_applyDetail();
},
); );
// anchorCtx = chip 自身位置:自定义范围下拉锚定在 chip 下方 // anchorCtx = chip 自身位置:自定义范围下拉锚定在 chip 下方
@@ -921,6 +953,10 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
// ── 详细搜索模态(11 字段)────────────────────────────────────── // ── 详细搜索模态(11 字段)──────────────────────────────────────
/// 详细搜索的「状态」字段仍单选(未扩展为多选):多选态无法单值呈现时归「全部」。
String get _statusForAdvDialog =>
_statusFilter.length == 1 ? _statusFilter.first : '';
Future<void> _openAdvSearch() async { Future<void> _openAdvSearch() async {
final initial = _AdvResult( final initial = _AdvResult(
orderNo: _orderNo, orderNo: _orderNo,
@@ -932,7 +968,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
spec: _spec, spec: _spec,
operatorId: _operatorId, operatorId: _operatorId,
reviewerId: _reviewerId, reviewerId: _reviewerId,
status: _statusFilter, status: _statusForAdvDialog,
dateRange: _dateRange, dateRange: _dateRange,
); );
final _AdvResult? result; final _AdvResult? result;
@@ -968,7 +1004,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
_spec = r.spec; _spec = r.spec;
_operatorId = r.operatorId; _operatorId = r.operatorId;
_reviewerId = r.reviewerId; _reviewerId = r.reviewerId;
_statusFilter = r.status; _statusFilter = r.status.isEmpty ? {} : {r.status};
_dateRange = r.dateRange; _dateRange = r.dateRange;
}); });
_applyAll(); _applyAll();
@@ -1573,6 +1609,102 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
} }
} }
// ── 状态筛选底部 sheet(原型 openStatusSheet 多选版):主状态 checkbox 多选 +
// 退单伪状态互斥单选,底部「重置/确定」应用(对齐 D3/D4/Step5)─────────────
class _StatusSheetResult {
final Set<String> statuses;
final String? retState; // null=未选退单态
const _StatusSheetResult(this.statuses, this.retState);
}
class _StatusMultiSheet extends StatefulWidget {
final Set<String> initialStatuses;
final String? initialRetState;
const _StatusMultiSheet({
super.key,
required this.initialStatuses,
required this.initialRetState,
});
@override
State<_StatusMultiSheet> createState() => _StatusMultiSheetState();
}
class _StatusMultiSheetState extends State<_StatusMultiSheet> {
late Set<String> _sel;
String? _ret;
@override
void initState() {
super.initState();
_sel = Set.of(widget.initialStatuses);
_ret = widget.initialRetState;
}
/// 重置(sheet actions「重置」调用):回默认「草稿+待审核」(D3)。
void reset() {
setState(() {
_sel = Set.of(_StockOutListScreenState._defaultStatuses);
_ret = null;
});
}
/// 确定(sheet actions「确定」调用):组装结果并关闭 sheet。
void apply() {
Navigator.of(context).pop(_StatusSheetResult(_sel, _ret));
}
@override
Widget build(BuildContext context) {
final t = context.tokens;
final mainCodes = _StockOutListScreenState._mainCodes;
final mainLabels = _StockOutListScreenState._mainLabels;
final retCodes = _StockOutListScreenState._retCodes;
final retLabels = _StockOutListScreenState._retLabels;
Widget row(String code, String label, {required bool isRet}) {
final sel = isRet ? _ret == code : _sel.contains(code);
return InkWell(
onTap: () => setState(() {
if (isRet) {
_ret = _ret == code ? null : code;
_sel = {};
} else {
final next = Set.of(_sel);
next.contains(code) ? next.remove(code) : next.add(code);
_sel = next;
_ret = null;
}
}),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 4),
decoration:
BoxDecoration(border: Border(bottom: BorderSide(color: t.borderSubtle))),
child: Row(children: [
DsIconBadge(label),
const SizedBox(width: 10),
Text(label,
style: TextStyle(
fontSize: AppDims.fsBody,
fontWeight: sel ? FontWeight.w600 : FontWeight.w400,
color: sel ? t.primary : t.text)),
const Spacer(),
if (sel) Icon(LucideIcons.check, size: 18, color: t.primary),
]),
),
);
}
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final c in mainCodes) row(c, mainLabels[c]!, isRet: false),
for (final r in retCodes) row(r, retLabels[r]!, isRet: true),
],
);
}
}
// ── 详细搜索模态框 ──────────────────────────────────────────────── // ── 详细搜索模态框 ────────────────────────────────────────────────
/// 详细搜索结果值(提交回父屏一次性应用)。 /// 详细搜索结果值(提交回父屏一次性应用)。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

+48 -11
View File
@@ -51,11 +51,15 @@ const ORDERS=[
{no:'RK-20260618-002',party:'泸州老窖仓',wh:'名酒仓',items:2,amount:'¥16,000',status:'已审核',date:'2026-06-18',by:'王经理',auditor:'王经理',goods:'泸州老窖 特曲、国窖 1573',lines:[{n:'泸州老窖 特曲',s:'500ml×6',q:25,a:'¥8,000',pd:'2022-02-14',b:'20260415'},{n:'国窖 1573',s:'500ml×6',q:8,a:'¥8,000',pd:'2021-04-09',b:'20260410'}]}, {no:'RK-20260618-002',party:'泸州老窖仓',wh:'名酒仓',items:2,amount:'¥16,000',status:'已审核',date:'2026-06-18',by:'王经理',auditor:'王经理',goods:'泸州老窖 特曲、国窖 1573',lines:[{n:'泸州老窖 特曲',s:'500ml×6',q:25,a:'¥8,000',pd:'2022-02-14',b:'20260415'},{n:'国窖 1573',s:'500ml×6',q:8,a:'¥8,000',pd:'2021-04-09',b:'20260410'}]},
{no:'RK-20260618-001',party:'洋河股份',wh:'主仓',items:2,amount:'¥14,800',status:'草稿',date:'2026-06-18',by:'王经理',auditor:'',goods:'洋河 梦之蓝 M6、洋河 海之蓝',lines:[{n:'洋河 梦之蓝 M6',s:'500ml×6',q:12,a:'¥7,440',pd:'2020-12-01',b:'20260322'},{n:'洋河 海之蓝',s:'500ml×6',q:16,a:'¥7,360',pd:'2022-06-11',b:'20260305'}]}, {no:'RK-20260618-001',party:'洋河股份',wh:'主仓',items:2,amount:'¥14,800',status:'草稿',date:'2026-06-18',by:'王经理',auditor:'',goods:'洋河 梦之蓝 M6、洋河 海之蓝',lines:[{n:'洋河 梦之蓝 M6',s:'500ml×6',q:12,a:'¥7,440',pd:'2020-12-01',b:'20260322'},{n:'洋河 海之蓝',s:'500ml×6',q:16,a:'¥7,360',pd:'2022-06-11',b:'20260305'}]},
]; ];
/* 状态词与桌面/后端对齐:主状态 4 词 + 退单态 2 词(return_state: partial/full */ /* 状态词与桌面/后端对齐:主状态 4 词 + 退单态 2 词(return_state: partial/full
const ST=['全部','草稿','待审核','已审核','已拒绝','部分退单','已退单']; 2026-07-14 改多选:主状态多选、退单伪状态与主状态互斥,默认「草稿+待审核」(D3 用户拍板)。 */
const MAIN_ST=['草稿','待审核','已审核','已拒绝'];
const RET_ST=['部分退单','已退单'];
const DEFAULT_ST=['草稿','待审核'];
const ST=['全部',...MAIN_ST,...RET_ST];
const OPS=['全部','王经理','李采购']; const OPS=['全部','王经理','李采购'];
const REVIEWERS=['全部','张主管','王经理']; const REVIEWERS=['全部','张主管','王经理'];
const state={q:'',status:'全部'}; const state={q:'',statusSet:new Set(DEFAULT_ST),ret:''};
/* ---- 状态代表图标(移动纯图标 .ico / 图标+文字 .bi;状态筛选钮为纯文字——用户拍板) ---- */ /* ---- 状态代表图标(移动纯图标 .ico / 图标+文字 .bi;状态筛选钮为纯文字——用户拍板) ---- */
const STICON={'草稿':'i-ic06','待审核':'i-ic04','已审核':'i-check','已拒绝':'i-close','部分退单':'i-undo','已退单':'i-undo','待定价':'i-yen'}; const STICON={'草稿':'i-ic06','待审核':'i-ic04','已审核':'i-check','已拒绝':'i-close','部分退单':'i-undo','已退单':'i-undo','待定价':'i-yen'};
function icoB(v){ return `<span class="badge ico b-${v}" title="${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg></span>`; } function icoB(v){ return `<span class="badge ico b-${v}" title="${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg></span>`; }
@@ -66,8 +70,8 @@ let adv={...ADV_DEF};
function advCount(){ return Object.keys(ADV_DEF).filter(k=>adv[k]!==ADV_DEF[k]).length; } function advCount(){ return Object.keys(ADV_DEF).filter(k=>adv[k]!==ADV_DEF[k]).length; }
function filtered(){ return ORDERS.filter(o=>{ function filtered(){ return ORDERS.filter(o=>{
const st=state.status; if(state.ret){ if(o.ret!==state.ret)return false; }
if(st!=='全部'&&o.status!==st&&o.ret!==st)return false; else if(state.statusSet.size&&!state.statusSet.has(o.status))return false;
if(state.q){const q=state.q.toLowerCase(); if(!o.no.toLowerCase().includes(q)&&!o.party.toLowerCase().includes(q)&&!(o.goods||'').toLowerCase().includes(q))return false;} if(state.q){const q=state.q.toLowerCase(); if(!o.no.toLowerCase().includes(q)&&!o.party.toLowerCase().includes(q)&&!(o.goods||'').toLowerCase().includes(q))return false;}
if(adv.no&&!o.no.toLowerCase().includes(adv.no.toLowerCase()))return false; if(adv.no&&!o.no.toLowerCase().includes(adv.no.toLowerCase()))return false;
if(adv.product){const p=adv.product.toLowerCase(); if(!(o.lines||[]).some(l=>l.n.toLowerCase().includes(p)))return false;} if(adv.product){const p=adv.product.toLowerCase(); if(!(o.lines||[]).some(l=>l.n.toLowerCase().includes(p)))return false;}
@@ -76,11 +80,20 @@ function filtered(){ return ORDERS.filter(o=>{
if(adv.reviewer!=='全部'&&o.auditor!==adv.reviewer)return false; if(adv.reviewer!=='全部'&&o.auditor!==adv.reviewer)return false;
if(adv.status!=='全部'&&o.status!==adv.status&&o.ret!==adv.status)return false; if(adv.status!=='全部'&&o.status!==adv.status&&o.ret!==adv.status)return false;
return true; }); } return true; }); }
// 状态摘要:无=全部、1 项=值本身、多项=「N 项」;退单态优先展示。
function statusLabel(){
if(state.ret) return state.ret;
if(!state.statusSet.size) return '全部';
if(state.statusSet.size===1) return [...state.statusSet][0];
return state.statusSet.size+' 项';
}
// 是否为「全部」(空集且无退单)——状态钮高亮判断:非「全部」即视为已筛选(含默认态)。
function isAllStatus(){ return !state.ret && !state.statusSet.size; }
function render(){ function render(){
const n=advCount(); const n=advCount();
document.getElementById('stVal').textContent=state.status; document.getElementById('stVal').textContent=statusLabel();
const ss=document.getElementById('stSel'); const ss=document.getElementById('stSel');
ss.classList.toggle('primary',state.status!=='全部'); ss.classList.toggle('ghost',state.status==='全部'); ss.classList.toggle('primary',!isAllStatus()); ss.classList.toggle('ghost',isAllStatus());
const ab=document.getElementById('advBtn'); const ab=document.getElementById('advBtn');
ab.classList.toggle('primary',n>0); ab.classList.toggle('ghost',n===0); ab.classList.toggle('primary',n>0); ab.classList.toggle('ghost',n===0);
const rows=filtered(); const rows=filtered();
@@ -96,11 +109,35 @@ function render(){
<div class="mc-foot"><svg width="13" height="13" viewBox="0 0 24 24" stroke-width="1.9" style="color:var(--muted)"><use href="#i-info"/></svg>${o.date} · 经手人 ${o.by}${o.auditor?' · 审核 '+o.auditor:''}</div> <div class="mc-foot"><svg width="13" height="13" viewBox="0 0 24 24" stroke-width="1.9" style="color:var(--muted)"><use href="#i-info"/></svg>${o.date} · 经手人 ${o.by}${o.auditor?' · 审核 '+o.auditor:''}</div>
</a>`).join(''); </a>`).join('');
} }
function setSt(s){ state.status=s; render(); } // KPI 卡点击 = 覆盖为该单值(D5,非 toggle 集合成员);'全部' 清空。
/* ---- 状态下拉(底部选项 sheet ---- */ function setSt(s){
function openStatusSheet(){ if(s==='全部'){ state.statusSet=new Set(); state.ret=''; }
openSheet('状态筛选', ST.map(s=>`<div class="m-opt ${s===state.status?'sel':''}" onclick="setSt('${s}');closeSheet()">${s!=='全部'?`<span class="badge ico b-${s}"><svg class="ic"><use href="#${STICON[s]}"/></svg></span>`:''}${s}<svg class="ck" viewBox="0 0 24 24"><use href="#i-check"/></svg></div>`).join('')); else if(RET_ST.includes(s)){ state.ret=s; state.statusSet=new Set(); }
else { state.ret=''; state.statusSet=new Set([s]); }
render();
} }
/* ---- 状态筛选(底部 sheet,多选勾选 + 确定应用):主状态多选 + 退单伪状态互斥 ---- */
let stDraft={sel:new Set(DEFAULT_ST),ret:''};
function openStatusSheet(){
stDraft={sel:new Set(state.statusSet),ret:state.ret};
drawStatusSheet();
}
function stOptRow(s,isRet){
const sel = isRet? stDraft.ret===s : stDraft.sel.has(s);
return `<div class="m-opt ${sel?'sel':''}" onclick="toggleStatusDraft('${s}',${isRet})"><span class="badge ico b-${s}"><svg class="ic"><use href="#${STICON[s]}"/></svg></span>${s}<svg class="ck" viewBox="0 0 24 24"><use href="#i-check"/></svg></div>`;
}
function drawStatusSheet(){
const rows = MAIN_ST.map(s=>stOptRow(s,false)).join('') + RET_ST.map(s=>stOptRow(s,true)).join('');
openSheet('单据状态', rows+`<div style="display:flex;gap:10px;margin-top:16px"><div class="btn ghost" style="flex:1;justify-content:center" onclick="resetStatusDraft()">重置</div><div class="btn primary" style="flex:2;justify-content:center" onclick="applyStatusDraft()">确定</div></div>`);
}
function toggleStatusDraft(s,isRet){
if(isRet){ stDraft.ret = stDraft.ret===s? '' : s; stDraft.sel=new Set(); }
else { stDraft.ret=''; stDraft.sel.has(s)?stDraft.sel.delete(s):stDraft.sel.add(s); }
drawStatusSheet();
}
// 重置(D3):回到默认「草稿+待审核」,不是回「全部」。
function resetStatusDraft(){ stDraft={sel:new Set(DEFAULT_ST),ret:''}; drawStatusSheet(); }
function applyStatusDraft(){ state.statusSet=new Set(stDraft.sel); state.ret=stDraft.ret; closeSheet(); render(); }
/* ==================== 详细搜索(底部 sheet,竖排单列) ==================== */ /* ==================== 详细搜索(底部 sheet,竖排单列) ==================== */
let advOpen=''; // 当前展开的下拉字段 let advOpen=''; // 当前展开的下拉字段
+48 -11
View File
@@ -50,11 +50,15 @@ const ORDERS=[
{no:'CK-20260618-004',party:'城东烟酒行',wh:'主仓',items:2,amount:'¥12,500',profit:'¥1,870',status:'已审核',ret:'已退单',date:'2026-06-18',by:'王经理',auditor:'王经理',goods:'西凤酒 旗舰版、郎酒 红花郎 15',lines:[{n:'西凤酒 旗舰版',s:'500ml×6',q:13,a:'¥6,500',pd:'2021-12-05',b:'20260325'},{n:'郎酒 红花郎 15',s:'500ml×6',q:10,a:'¥6,000',pd:'2018-09-16',b:'20260118'}]}, {no:'CK-20260618-004',party:'城东烟酒行',wh:'主仓',items:2,amount:'¥12,500',profit:'¥1,870',status:'已审核',ret:'已退单',date:'2026-06-18',by:'王经理',auditor:'王经理',goods:'西凤酒 旗舰版、郎酒 红花郎 15',lines:[{n:'西凤酒 旗舰版',s:'500ml×6',q:13,a:'¥6,500',pd:'2021-12-05',b:'20260325'},{n:'郎酒 红花郎 15',s:'500ml×6',q:10,a:'¥6,000',pd:'2018-09-16',b:'20260118'}]},
{no:'CK-20260618-003',party:'金樽会所',wh:'名酒仓',items:1,amount:'¥8,400',profit:'¥1,260',status:'草稿',date:'2026-06-18',by:'李销售',auditor:'',goods:'汾酒 青花 20',lines:[{n:'汾酒 青花 20',s:'500ml×6',q:12,a:'¥8,400',pd:'2021-01-27',b:'20260506'}]}, {no:'CK-20260618-003',party:'金樽会所',wh:'名酒仓',items:1,amount:'¥8,400',profit:'¥1,260',status:'草稿',date:'2026-06-18',by:'李销售',auditor:'',goods:'汾酒 青花 20',lines:[{n:'汾酒 青花 20',s:'500ml×6',q:12,a:'¥8,400',pd:'2021-01-27',b:'20260506'}]},
]; ];
/* 状态词与桌面/后端对齐:主状态 4 词 + 退单态 2 词(return_state: partial/full */ /* 状态词与桌面/后端对齐:主状态 4 词 + 退单态 2 词(return_state: partial/full
const ST=['全部','草稿','待审核','已审核','已拒绝','部分退单','已退单']; 2026-07-14 改多选:主状态多选、退单伪状态与主状态互斥,默认「草稿+待审核」(D3 用户拍板)。 */
const MAIN_ST=['草稿','待审核','已审核','已拒绝'];
const RET_ST=['部分退单','已退单'];
const DEFAULT_ST=['草稿','待审核'];
const ST=['全部',...MAIN_ST,...RET_ST];
const OPS=['全部','王经理','李销售']; const OPS=['全部','王经理','李销售'];
const REVIEWERS=['全部','张主管','王经理']; const REVIEWERS=['全部','张主管','王经理'];
const state={q:'',status:'全部'}; const state={q:'',statusSet:new Set(DEFAULT_ST),ret:''};
/* ---- 状态代表图标(移动纯图标 .ico / 图标+文字 .bi;状态筛选钮为纯文字——用户拍板) ---- */ /* ---- 状态代表图标(移动纯图标 .ico / 图标+文字 .bi;状态筛选钮为纯文字——用户拍板) ---- */
const STICON={'草稿':'i-ic06','待审核':'i-ic04','已审核':'i-check','已拒绝':'i-close','部分退单':'i-undo','已退单':'i-undo','待定价':'i-yen'}; const STICON={'草稿':'i-ic06','待审核':'i-ic04','已审核':'i-check','已拒绝':'i-close','部分退单':'i-undo','已退单':'i-undo','待定价':'i-yen'};
function icoB(v){ return `<span class="badge ico b-${v}" title="${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg></span>`; } function icoB(v){ return `<span class="badge ico b-${v}" title="${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg></span>`; }
@@ -65,8 +69,8 @@ let adv={...ADV_DEF};
function advCount(){ return Object.keys(ADV_DEF).filter(k=>adv[k]!==ADV_DEF[k]).length; } function advCount(){ return Object.keys(ADV_DEF).filter(k=>adv[k]!==ADV_DEF[k]).length; }
function filtered(){ return ORDERS.filter(o=>{ function filtered(){ return ORDERS.filter(o=>{
const st=state.status; if(state.ret){ if(o.ret!==state.ret)return false; }
if(st!=='全部'&&o.status!==st&&o.ret!==st)return false; else if(state.statusSet.size&&!state.statusSet.has(o.status))return false;
if(state.q){const q=state.q.toLowerCase(); if(!o.no.toLowerCase().includes(q)&&!o.party.toLowerCase().includes(q)&&!(o.goods||'').toLowerCase().includes(q))return false;} if(state.q){const q=state.q.toLowerCase(); if(!o.no.toLowerCase().includes(q)&&!o.party.toLowerCase().includes(q)&&!(o.goods||'').toLowerCase().includes(q))return false;}
if(adv.no&&!o.no.toLowerCase().includes(adv.no.toLowerCase()))return false; if(adv.no&&!o.no.toLowerCase().includes(adv.no.toLowerCase()))return false;
if(adv.product){const p=adv.product.toLowerCase(); if(!(o.lines||[]).some(l=>l.n.toLowerCase().includes(p)))return false;} if(adv.product){const p=adv.product.toLowerCase(); if(!(o.lines||[]).some(l=>l.n.toLowerCase().includes(p)))return false;}
@@ -75,11 +79,20 @@ function filtered(){ return ORDERS.filter(o=>{
if(adv.reviewer!=='全部'&&o.auditor!==adv.reviewer)return false; if(adv.reviewer!=='全部'&&o.auditor!==adv.reviewer)return false;
if(adv.status!=='全部'&&o.status!==adv.status&&o.ret!==adv.status)return false; if(adv.status!=='全部'&&o.status!==adv.status&&o.ret!==adv.status)return false;
return true; }); } return true; }); }
// 状态摘要:无=全部、1 项=值本身、多项=「N 项」;退单态优先展示。
function statusLabel(){
if(state.ret) return state.ret;
if(!state.statusSet.size) return '全部';
if(state.statusSet.size===1) return [...state.statusSet][0];
return state.statusSet.size+' 项';
}
// 是否为「全部」(空集且无退单)——状态钮高亮判断:非「全部」即视为已筛选(含默认态)。
function isAllStatus(){ return !state.ret && !state.statusSet.size; }
function render(){ function render(){
const n=advCount(); const n=advCount();
document.getElementById('stVal').textContent=state.status; document.getElementById('stVal').textContent=statusLabel();
const ss=document.getElementById('stSel'); const ss=document.getElementById('stSel');
ss.classList.toggle('primary',state.status!=='全部'); ss.classList.toggle('ghost',state.status==='全部'); ss.classList.toggle('primary',!isAllStatus()); ss.classList.toggle('ghost',isAllStatus());
const ab=document.getElementById('advBtn'); const ab=document.getElementById('advBtn');
ab.classList.toggle('primary',n>0); ab.classList.toggle('ghost',n===0); ab.classList.toggle('primary',n>0); ab.classList.toggle('ghost',n===0);
const rows=filtered(); const rows=filtered();
@@ -95,11 +108,35 @@ function render(){
<div class="mc-foot"><svg width="13" height="13" viewBox="0 0 24 24" stroke-width="1.9" style="color:var(--muted)"><use href="#i-info"/></svg>${o.date} · 经手人 ${o.by}${o.auditor?' · 审核 '+o.auditor:''}</div> <div class="mc-foot"><svg width="13" height="13" viewBox="0 0 24 24" stroke-width="1.9" style="color:var(--muted)"><use href="#i-info"/></svg>${o.date} · 经手人 ${o.by}${o.auditor?' · 审核 '+o.auditor:''}</div>
</a>`).join(''); </a>`).join('');
} }
function setSt(s){ state.status=s; render(); } // KPI 卡点击 = 覆盖为该单值(D5,非 toggle 集合成员);'全部' 清空。
/* ---- 状态下拉(底部选项 sheet ---- */ function setSt(s){
function openStatusSheet(){ if(s==='全部'){ state.statusSet=new Set(); state.ret=''; }
openSheet('状态筛选', ST.map(s=>`<div class="m-opt ${s===state.status?'sel':''}" onclick="setSt('${s}');closeSheet()">${s!=='全部'?`<span class="badge ico b-${s}"><svg class="ic"><use href="#${STICON[s]}"/></svg></span>`:''}${s}<svg class="ck" viewBox="0 0 24 24"><use href="#i-check"/></svg></div>`).join('')); else if(RET_ST.includes(s)){ state.ret=s; state.statusSet=new Set(); }
else { state.ret=''; state.statusSet=new Set([s]); }
render();
} }
/* ---- 状态筛选(底部 sheet,多选勾选 + 确定应用):主状态多选 + 退单伪状态互斥 ---- */
let stDraft={sel:new Set(DEFAULT_ST),ret:''};
function openStatusSheet(){
stDraft={sel:new Set(state.statusSet),ret:state.ret};
drawStatusSheet();
}
function stOptRow(s,isRet){
const sel = isRet? stDraft.ret===s : stDraft.sel.has(s);
return `<div class="m-opt ${sel?'sel':''}" onclick="toggleStatusDraft('${s}',${isRet})"><span class="badge ico b-${s}"><svg class="ic"><use href="#${STICON[s]}"/></svg></span>${s}<svg class="ck" viewBox="0 0 24 24"><use href="#i-check"/></svg></div>`;
}
function drawStatusSheet(){
const rows = MAIN_ST.map(s=>stOptRow(s,false)).join('') + RET_ST.map(s=>stOptRow(s,true)).join('');
openSheet('单据状态', rows+`<div style="display:flex;gap:10px;margin-top:16px"><div class="btn ghost" style="flex:1;justify-content:center" onclick="resetStatusDraft()">重置</div><div class="btn primary" style="flex:2;justify-content:center" onclick="applyStatusDraft()">确定</div></div>`);
}
function toggleStatusDraft(s,isRet){
if(isRet){ stDraft.ret = stDraft.ret===s? '' : s; stDraft.sel=new Set(); }
else { stDraft.ret=''; stDraft.sel.has(s)?stDraft.sel.delete(s):stDraft.sel.add(s); }
drawStatusSheet();
}
// 重置(D3):回到默认「草稿+待审核」,不是回「全部」。
function resetStatusDraft(){ stDraft={sel:new Set(DEFAULT_ST),ret:''}; drawStatusSheet(); }
function applyStatusDraft(){ state.statusSet=new Set(stDraft.sel); state.ret=stDraft.ret; closeSheet(); render(); }
/* ==================== 详细搜索(底部 sheet,竖排单列) ==================== */ /* ==================== 详细搜索(底部 sheet,竖排单列) ==================== */
let advOpen=''; let advOpen='';
+64 -13
View File
@@ -24,6 +24,11 @@
.toolbar .daterange svg{width:14px; height:14px; stroke-width:1.8; color:var(--faint);} .toolbar .daterange svg{width:14px; height:14px; stroke-width:1.8; color:var(--faint);}
.toolbar .daterange .dv{color:var(--text);} .toolbar .daterange .dv{color:var(--text);}
.toolbar .sp{margin-left:auto;} .toolbar .sp{margin-left:auto;}
/* chip 尾部图标切换:未筛选=下拉箭头,筛选中=× 单清(镜像 inventory.html 多选 chip,待提升至 atoms.css */
.chip .cx{display:none;}
.chip.on .cx{display:block; cursor:pointer; color:var(--muted);}
.chip.on .cx:hover{color:var(--text);}
.chip.on .caret{display:none;}
/* ---- 状态徽章(新增变体,待提升至 atoms.css ---- */ /* ---- 状态徽章(新增变体,待提升至 atoms.css ---- */
.badge.b-草稿{background:var(--info-soft); color:var(--muted);} .badge.b-草稿{background:var(--info-soft); color:var(--muted);}
@@ -64,7 +69,7 @@
<div class="searchbox"><svg viewBox="0 0 24 24"><use href="#i-search"/></svg><input id="searchInput" placeholder="搜索单号 / 供应商 / 酒名" oninput="state.q=this.value; state.page=1; render()"></div> <div class="searchbox"><svg viewBox="0 0 24 24"><use href="#i-search"/></svg><input id="searchInput" placeholder="搜索单号 / 供应商 / 酒名" oninput="state.q=this.value; state.page=1; render()"></div>
<div class="chip" id="chipParty" onclick="openPartyFilter(this)">供应商 <span class="cv" id="partyVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div> <div class="chip" id="chipParty" onclick="openPartyFilter(this)">供应商 <span class="cv" id="partyVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipWh" onclick="openWhFilter(this)">仓库 <span class="cv" id="whVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div> <div class="chip" id="chipWh" onclick="openWhFilter(this)">仓库 <span class="cv" id="whVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipStatus" onclick="openStatusFilter(this)">状态 <span class="cv" id="statusVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div> <div class="chip" id="chipStatus" onclick="openStatusFilter(this)">状态 <span class="cv" id="statusVal"></span><svg class="cx" viewBox="0 0 24 24" onclick="clearStatus(event)"><use href="#i-close"/></svg><svg class="caret" viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipDate" onclick="openDateFilter(this)"><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg>入库时间 <span class="cv" id="dateVal"></span></div> <div class="chip" id="chipDate" onclick="openDateFilter(this)"><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg>入库时间 <span class="cv" id="dateVal"></span></div>
<div class="btn soft sm" id="advBtn" onclick="openAdvSearch()"><svg viewBox="0 0 24 24"><use href="#i-ic21"/></svg>详细搜索<span class="cv" id="advCount"></span></div> <div class="btn soft sm" id="advBtn" onclick="openAdvSearch()"><svg viewBox="0 0 24 24"><use href="#i-ic21"/></svg>详细搜索<span class="cv" id="advCount"></span></div>
<div class="sp"></div> <div class="sp"></div>
@@ -144,9 +149,14 @@ const ORDERS=[
{no:'RK-20260613-001',date:'2026-06-13',party:'西凤酒陕西总代',wh:'主仓',count:1,amount:6720,status:'草稿',op:'张主管',auditor:'',lines:[{name:'西凤酒 旗舰版',qty:24,price:280}]}, {no:'RK-20260613-001',date:'2026-06-13',party:'西凤酒陕西总代',wh:'主仓',count:1,amount:6720,status:'草稿',op:'张主管',auditor:'',lines:[{name:'西凤酒 旗舰版',qty:24,price:280}]},
{no:'RK-20260612-003',date:'2026-06-12',party:'水井坊川渝仓',wh:'名酒仓',count:1,amount:16320,status:'待审核',op:'王经理',auditor:'',lines:[{name:'水井坊 井台',qty:24,price:680}]}, {no:'RK-20260612-003',date:'2026-06-12',party:'水井坊川渝仓',wh:'名酒仓',count:1,amount:16320,status:'待审核',op:'王经理',auditor:'',lines:[{name:'水井坊 井台',qty:24,price:680}]},
]; ];
const STATUS=['全部','草稿','待审核','已审核','已拒绝','部分退单','已退单']; // 状态筛选 2026-07-14 改多选:主状态(草稿/待审核/已审核/已拒绝)可多选,退单伪状态(部分退单/已退单)
// 与主状态互斥(选其一清另一侧)。默认「草稿+待审核」(D3 用户拍板)。
const MAIN_STATUS=['草稿','待审核','已审核','已拒绝'];
const RET_STATUS=['部分退单','已退单'];
const DEFAULT_STATUS=['草稿','待审核'];
const STATUS=['全部',...MAIN_STATUS,...RET_STATUS]; // 供详细搜索单选字段沿用
const COLS=[{key:'no',label:'单号'},{key:'party',label:'供应商'},{key:'wh',label:'仓库'},{key:'amount',label:'合计金额',num:true},{key:'status',label:'状态'},{key:'op',label:'入库员'},{key:'auditor',label:'审核员'},{key:'act',label:'操作',fixed:true}]; const COLS=[{key:'no',label:'单号'},{key:'party',label:'供应商'},{key:'wh',label:'仓库'},{key:'amount',label:'合计金额',num:true},{key:'status',label:'状态'},{key:'op',label:'入库员'},{key:'auditor',label:'审核员'},{key:'act',label:'操作',fixed:true}];
const state={q:'',party:'全部',wh:'全部',status:'全部',dateFrom:'',dateTo:'',page:1,perPage:10,orderNo:'',productInfo:'',batch:'',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部'}; const state={q:'',party:'全部',wh:'全部',statusSet:new Set(DEFAULT_STATUS),ret:'',dateFrom:'',dateTo:'',page:1,perPage:10,orderNo:'',productInfo:'',batch:'',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部'};
const PARTIES=['全部',...Array.from(new Set(ORDERS.map(o=>o.party)))]; const PARTIES=['全部',...Array.from(new Set(ORDERS.map(o=>o.party)))];
const WAREHOUSES=['全部',...Array.from(new Set(ORDERS.map(o=>o.wh)))]; const WAREHOUSES=['全部',...Array.from(new Set(ORDERS.map(o=>o.wh)))];
@@ -159,7 +169,8 @@ const STICON={'草稿':'i-ic06','待审核':'i-ic04','已审核':'i-check','已
function stBadge(v){ return `<span class="badge bi b-${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg>${v}</span>`; } function stBadge(v){ return `<span class="badge bi b-${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg>${v}</span>`; }
function numf(n){ return (Number(n)||0).toLocaleString(); } /* 明细行内数字:去¥,缩一号(2026-07-03 */ function numf(n){ return (Number(n)||0).toLocaleString(); } /* 明细行内数字:去¥,缩一号(2026-07-03 */
function filtered(){ return ORDERS.filter(o=>{ function filtered(){ return ORDERS.filter(o=>{
if(state.status!=='全部'&&o.status!==state.status&&o.ret!==state.status)return false; if(state.ret){ if(o.ret!==state.ret)return false; }
else if(state.statusSet.size&&!state.statusSet.has(o.status))return false;
if(state.party!=='全部'&&o.party!==state.party)return false; if(state.party!=='全部'&&o.party!==state.party)return false;
if(state.wh!=='全部'&&o.wh!==state.wh)return false; if(state.wh!=='全部'&&o.wh!==state.wh)return false;
if(state.dateFrom&&o.date<state.dateFrom)return false; if(state.dateFrom&&o.date<state.dateFrom)return false;
@@ -176,11 +187,20 @@ function filtered(){ return ORDERS.filter(o=>{
if(state.category&&state.category!=='全部'&&!L.some(l=>(PINFO[l.name]||{}).cat===state.category))return false; if(state.category&&state.category!=='全部'&&!L.some(l=>(PINFO[l.name]||{}).cat===state.category))return false;
if(state.batch){const b=state.batch.toLowerCase(); if(!L.some(l=>{const pi=PINFO[l.name]||{}; return (pi.batch||'').toLowerCase().includes(b)||(pi.code||'').toLowerCase().includes(b);}))return false;} if(state.batch){const b=state.batch.toLowerCase(); if(!L.some(l=>{const pi=PINFO[l.name]||{}; return (pi.batch||'').toLowerCase().includes(b)||(pi.code||'').toLowerCase().includes(b);}))return false;}
return true; }); } return true; }); }
// 状态 chip 摘要(镜像 inventory.html 多选 chip):无=空、1 项=值本身、多项=「N 项」;退单态优先展示。
function statusChipLabel(){
if(state.ret) return state.ret;
if(!state.statusSet.size) return '';
if(state.statusSet.size===1) return [...state.statusSet][0];
return state.statusSet.size+' 项';
}
// 状态是否为默认态(草稿+待审核、无退单)——仅用于「重置」按钮高亮判断,避免默认态常年高亮。
function isDefaultStatus(){ return !state.ret && state.statusSet.size===2 && state.statusSet.has('草稿') && state.statusSet.has('待审核'); }
function syncChips(){ function syncChips(){
const set=(id,chipId,val)=>{ document.getElementById(id).textContent=val||''; document.getElementById(chipId).classList.toggle('on',!!val); }; const set=(id,chipId,val)=>{ document.getElementById(id).textContent=val||''; document.getElementById(chipId).classList.toggle('on',!!val); };
set('partyVal','chipParty',state.party==='全部'?'':state.party); set('partyVal','chipParty',state.party==='全部'?'':state.party);
set('whVal','chipWh',state.wh==='全部'?'':state.wh); set('whVal','chipWh',state.wh==='全部'?'':state.wh);
set('statusVal','chipStatus',state.status==='全部'?'':state.status); set('statusVal','chipStatus',statusChipLabel());
set('dateVal','chipDate',state.dateLabel||''); set('dateVal','chipDate',state.dateLabel||'');
const advCount=[state.orderNo,state.productInfo,state.batch, const advCount=[state.orderNo,state.productInfo,state.batch,
(state.series&&state.series!=='全部')?1:0, (state.series&&state.series!=='全部')?1:0,
@@ -192,13 +212,13 @@ function syncChips(){
advBtnEl.classList.toggle('primary',advCount>0); advBtnEl.classList.toggle('primary',advCount>0);
advBtnEl.classList.toggle('soft',advCount===0); advBtnEl.classList.toggle('soft',advCount===0);
document.getElementById('advCount').textContent=advCount?' · '+advCount:''; document.getElementById('advCount').textContent=advCount?' · '+advCount:'';
const any=state.q||state.party!=='全部'||state.wh!=='全部'||state.status!=='全部'||state.dateFrom||state.dateTo||advCount>0; const any=state.q||state.party!=='全部'||state.wh!=='全部'||!isDefaultStatus()||state.dateFrom||state.dateTo||advCount>0;
document.getElementById('resetBtn').classList.toggle('primary',!!any); document.getElementById('resetBtn').classList.toggle('primary',!!any);
} }
function render(){ function render(){
syncChips(); syncChips();
const cols=COLS; const cols=COLS;
document.getElementById('thead').innerHTML='<tr>'+cols.map(c=>{ const cls=(c.num?'num ':'')+(c.key==='act'?'act ':''); if(c.filter){const on=state.status!=='全部'; return `<th class="${cls}"><span class="fh ${on?'filtered':''}" onclick="openFilter(this)">${c.label} <svg class="funnel" viewBox="0 0 24 24"><use href="#i-ic22"/></svg></span></th>`;} return `<th class="${cls}">${c.label}</th>`; }).join('')+'</tr>'; document.getElementById('thead').innerHTML='<tr>'+cols.map(c=>{ const cls=(c.num?'num ':'')+(c.key==='act'?'act ':''); if(c.filter){const on=!isDefaultStatus(); return `<th class="${cls}"><span class="fh ${on?'filtered':''}" onclick="openFilter(this)">${c.label} <svg class="funnel" viewBox="0 0 24 24"><use href="#i-ic22"/></svg></span></th>`;} return `<th class="${cls}">${c.label}</th>`; }).join('')+'</tr>';
const rows=filtered(); const start=(state.page-1)*state.perPage; const pageRows=rows.slice(start,start+state.perPage); const tb=document.getElementById('tbody'); const rows=filtered(); const start=(state.page-1)*state.perPage; const pageRows=rows.slice(start,start+state.perPage); const tb=document.getElementById('tbody');
if(rows.length===0){ tb.innerHTML=`<tr><td colspan="${cols.length}"><div class="empty">没有匹配的入库单 · 试试调整筛选或搜索</div></td></tr>`; } if(rows.length===0){ tb.innerHTML=`<tr><td colspan="${cols.length}"><div class="empty">没有匹配的入库单 · 试试调整筛选或搜索</div></td></tr>`; }
else { tb.innerHTML=pageRows.map(o=>{ const idx=ORDERS.indexOf(o); return '<tr class="click" onclick="openDetail('+idx+')">'+cols.map(c=>{ if(c.key==='no')return `<td><span class="pcode" style="font-size:var(--fs-body)">${o.no}</span></td>`; if(c.key==='party')return `<td class="pname">${o.party}</td>`; if(c.key==='wh')return `<td>${o.wh}</td>`; if(c.key==='amount')return `<td class="num">${money(o.amount)}</td>`; if(c.key==='status')return `<td>${stBadge(o.status)}${o.ret?' '+stBadge(o.ret):''}</td>`; if(c.key==='op')return `<td>${o.op}</td>`; if(c.key==='auditor')return `<td>${o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>'}</td>`; if(c.key==='act')return `<td class="act"><div class="row-act" onclick="event.stopPropagation()"><svg viewBox="0 0 24 24" onclick="openDetail(${idx})"><use href="#i-eye"/></svg></div></td>`; return `<td>${o[c.key]}</td>`; }).join('')+'</tr>'; }).join(''); } else { tb.innerHTML=pageRows.map(o=>{ const idx=ORDERS.indexOf(o); return '<tr class="click" onclick="openDetail('+idx+')">'+cols.map(c=>{ if(c.key==='no')return `<td><span class="pcode" style="font-size:var(--fs-body)">${o.no}</span></td>`; if(c.key==='party')return `<td class="pname">${o.party}</td>`; if(c.key==='wh')return `<td>${o.wh}</td>`; if(c.key==='amount')return `<td class="num">${money(o.amount)}</td>`; if(c.key==='status')return `<td>${stBadge(o.status)}${o.ret?' '+stBadge(o.ret):''}</td>`; if(c.key==='op')return `<td>${o.op}</td>`; if(c.key==='auditor')return `<td>${o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>'}</td>`; if(c.key==='act')return `<td class="act"><div class="row-act" onclick="event.stopPropagation()"><svg viewBox="0 0 24 24" onclick="openDetail(${idx})"><use href="#i-eye"/></svg></div></td>`; return `<td>${o[c.key]}</td>`; }).join('')+'</tr>'; }).join(''); }
@@ -210,10 +230,32 @@ function go(p){ const pages=Math.max(1,Math.ceil(filtered().length/state.perPage
const PAGE_SIZES=[10,20,50,100]; const PAGE_SIZES=[10,20,50,100];
function openPageSize(e){ openMenu(e.currentTarget, PAGE_SIZES.map(n=>({v:String(n),label:String(n),sel:n===state.perPage})), v=>{ closeMenus(); setPerPage(v); }, 'pgsize-menu', 88); } function openPageSize(e){ openMenu(e.currentTarget, PAGE_SIZES.map(n=>({v:String(n),label:String(n),sel:n===state.perPage})), v=>{ closeMenus(); setPerPage(v); }, 'pgsize-menu', 88); }
function setPerPage(v){ state.perPage=+v; state.page=1; const el=document.getElementById('pageSizeVal'); if(el)el.textContent=v; render(); } function setPerPage(v){ state.perPage=+v; state.page=1; const el=document.getElementById('pageSizeVal'); if(el)el.textContent=v; render(); }
function filterStatus(s){ state.status=s; state.page=1; render(); } // KPI 卡点击 = 覆盖为该单值(D5,非 toggle 集合成员)。
function clearFilters(){ state.q='';state.party='全部';state.wh='全部';state.status='全部';state.dateFrom='';state.dateTo='';state.dateLabel='';state.datePreset='all';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';state.page=1; document.getElementById('searchInput').value=''; render(); } function filterStatus(s){
if(RET_STATUS.includes(s)){ state.ret=s; state.statusSet=new Set(); }
else { state.ret=''; state.statusSet=new Set([s]); }
state.page=1; render();
}
// 状态 chip 的 × 单清:回到默认「草稿+待审核」,不是回「全部」(D3)。
function clearStatus(e){ e.stopPropagation(); state.statusSet=new Set(DEFAULT_STATUS); state.ret=''; state.page=1; render(); }
function clearFilters(){ state.q='';state.party='全部';state.wh='全部';state.statusSet=new Set(DEFAULT_STATUS);state.ret='';state.dateFrom='';state.dateTo='';state.dateLabel='';state.datePreset='all';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';state.page=1; document.getElementById('searchInput').value=''; render(); }
function openStatusFilter(t){ openMenu(t,STATUS.map(v=>({v,label:v,sel:v===state.status})),v=>{ state.status=v; state.page=1; closeMenus(); render(); }); } // 状态多选菜单:主状态 checkbox 多选 + 退单伪状态互斥(选其一清主状态多选,反之亦然);
// 菜单勾选后不关闭(镜像 inventory.html openDimFilter 的重开手法)。
function openStatusFilter(t){
const opts=[...MAIN_STATUS.map(v=>({v,label:v,sel:state.statusSet.has(v)})),
...RET_STATUS.map(v=>({v,label:v,sel:state.ret===v}))];
openMenu(t,opts,v=>{
if(RET_STATUS.includes(v)){
state.ret = state.ret===v ? '' : v;
state.statusSet=new Set();
} else {
state.ret='';
state.statusSet.has(v)?state.statusSet.delete(v):state.statusSet.add(v);
}
state.page=1; render(); openStatusFilter(t);
});
}
function openPartyFilter(t){ openSearchMenu(t,PARTIES.map(v=>({v,label:v,sel:v===state.party})),v=>{ state.party=v; state.page=1; closeMenus(); render(); },{placeholder:'搜索供应商…'}); } function openPartyFilter(t){ openSearchMenu(t,PARTIES.map(v=>({v,label:v,sel:v===state.party})),v=>{ state.party=v; state.page=1; closeMenus(); render(); },{placeholder:'搜索供应商…'}); }
function openWhFilter(t){ openMenu(t,WAREHOUSES.map(v=>({v,label:v,sel:v===state.wh})),v=>{ state.wh=v; state.page=1; closeMenus(); render(); }); } function openWhFilter(t){ openMenu(t,WAREHOUSES.map(v=>({v,label:v,sel:v===state.wh})),v=>{ state.wh=v; state.page=1; closeMenus(); render(); }); }
const TODAY='2026-06-22'; const TODAY='2026-06-22';
@@ -264,8 +306,14 @@ const OP_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.op)))];
const REVIEWER_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.auditor).filter(Boolean)))]; const REVIEWER_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.auditor).filter(Boolean)))];
let adv={}; let adv={};
function advSet(id,val){ const el=document.getElementById(id); if(el)el.value=(!val||val==='全部')?'':val; } function advSet(id,val){ const el=document.getElementById(id); if(el)el.value=(!val||val==='全部')?'':val; }
// 详细搜索的「状态」字段仍是单选(原型/Flutter 均未扩展为多选):多选态无法单值呈现时归「全部」。
function currentStatusForAdv(){
if(state.ret) return state.ret;
if(state.statusSet.size===1) return [...state.statusSet][0];
return '全部';
}
function openAdvSearch(){ function openAdvSearch(){
adv={party:state.party,series:state.series,spec:state.spec,category:state.category,op:state.op,reviewer:state.reviewer,status:state.status,dateFrom:state.dateFrom||'',dateTo:state.dateTo||''}; adv={party:state.party,series:state.series,spec:state.spec,category:state.category,op:state.op,reviewer:state.reviewer,status:currentStatusForAdv(),dateFrom:state.dateFrom||'',dateTo:state.dateTo||''};
advSet('advParty',adv.party); advSet('advSeries',adv.series); advSet('advSpec',adv.spec); advSet('advCategory',adv.category); advSet('advParty',adv.party); advSet('advSeries',adv.series); advSet('advSpec',adv.spec); advSet('advCategory',adv.category);
advSet('advOp',adv.op); advSet('advReviewer',adv.reviewer); advSet('advStatus',adv.status); advSet('advOp',adv.op); advSet('advReviewer',adv.reviewer); advSet('advStatus',adv.status);
document.getElementById('advNo').value=state.orderNo||''; document.getElementById('advNo').value=state.orderNo||'';
@@ -290,7 +338,10 @@ function applyAdv(){
state.productInfo=document.getElementById('advProduct').value.trim(); state.productInfo=document.getElementById('advProduct').value.trim();
state.batch=document.getElementById('advBatch').value.trim(); state.batch=document.getElementById('advBatch').value.trim();
state.party=adv.party; state.series=adv.series; state.spec=adv.spec; state.category=adv.category; state.party=adv.party; state.series=adv.series; state.spec=adv.spec; state.category=adv.category;
state.op=adv.op; state.reviewer=adv.reviewer; state.status=adv.status; state.op=adv.op; state.reviewer=adv.reviewer;
if(adv.status==='全部'){ state.statusSet=new Set(); state.ret=''; }
else if(RET_STATUS.includes(adv.status)){ state.ret=adv.status; state.statusSet=new Set(); }
else { state.statusSet=new Set([adv.status]); state.ret=''; }
state.dateFrom=adv.dateFrom; state.dateTo=adv.dateTo; state.dateFrom=adv.dateFrom; state.dateTo=adv.dateTo;
state.dateLabel=adv.dateFrom?adv.dateFrom+' ~ '+adv.dateTo:''; state.datePreset=adv.dateFrom?'custom':'all'; state.dateLabel=adv.dateFrom?adv.dateFrom+' ~ '+adv.dateTo:''; state.datePreset=adv.dateFrom?'custom':'all';
state.page=1; closeOverlay('ovAdv'); render(); state.page=1; closeOverlay('ovAdv'); render();
@@ -298,7 +349,7 @@ function applyAdv(){
function resetAdv(){ function resetAdv(){
adv={party:'全部',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部',status:'全部',dateFrom:'',dateTo:''}; adv={party:'全部',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部',status:'全部',dateFrom:'',dateTo:''};
['advNo','advProduct','advBatch','advParty','advSeries','advSpec','advCategory','advOp','advReviewer','advStatus','advDate'].forEach(id=>{const el=document.getElementById(id); if(el)el.value='';}); ['advNo','advProduct','advBatch','advParty','advSeries','advSpec','advCategory','advOp','advReviewer','advStatus','advDate'].forEach(id=>{const el=document.getElementById(id); if(el)el.value='';});
state.party='全部';state.status='全部';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部'; state.party='全部';state.statusSet=new Set();state.ret='';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';
applyDatePreset('all'); state.page=1; render(); applyDatePreset('all'); state.page=1; render();
} }
function auditBtns(o,idx){ function auditBtns(o,idx){
+64 -13
View File
@@ -24,6 +24,11 @@
.toolbar .daterange svg{width:14px; height:14px; stroke-width:1.8; color:var(--faint);} .toolbar .daterange svg{width:14px; height:14px; stroke-width:1.8; color:var(--faint);}
.toolbar .daterange .dv{color:var(--text);} .toolbar .daterange .dv{color:var(--text);}
.toolbar .sp{margin-left:auto;} .toolbar .sp{margin-left:auto;}
/* chip 尾部图标切换:未筛选=下拉箭头,筛选中=× 单清(镜像 inventory.html 多选 chip,待提升至 atoms.css */
.chip .cx{display:none;}
.chip.on .cx{display:block; cursor:pointer; color:var(--muted);}
.chip.on .cx:hover{color:var(--text);}
.chip.on .caret{display:none;}
/* ---- 状态徽章(新增变体,待提升至 atoms.css ---- */ /* ---- 状态徽章(新增变体,待提升至 atoms.css ---- */
.badge.b-草稿{background:var(--info-soft); color:var(--muted);} .badge.b-草稿{background:var(--info-soft); color:var(--muted);}
@@ -64,7 +69,7 @@
<div class="searchbox"><svg viewBox="0 0 24 24"><use href="#i-search"/></svg><input id="searchInput" placeholder="搜索单号 / 客户 / 酒名" oninput="state.q=this.value; state.page=1; render()"></div> <div class="searchbox"><svg viewBox="0 0 24 24"><use href="#i-search"/></svg><input id="searchInput" placeholder="搜索单号 / 客户 / 酒名" oninput="state.q=this.value; state.page=1; render()"></div>
<div class="chip" id="chipParty" onclick="openPartyFilter(this)">客户 <span class="cv" id="partyVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div> <div class="chip" id="chipParty" onclick="openPartyFilter(this)">客户 <span class="cv" id="partyVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipWh" onclick="openWhFilter(this)">仓库 <span class="cv" id="whVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div> <div class="chip" id="chipWh" onclick="openWhFilter(this)">仓库 <span class="cv" id="whVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipStatus" onclick="openStatusFilter(this)">状态 <span class="cv" id="statusVal"></span><svg viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div> <div class="chip" id="chipStatus" onclick="openStatusFilter(this)">状态 <span class="cv" id="statusVal"></span><svg class="cx" viewBox="0 0 24 24" onclick="clearStatus(event)"><use href="#i-close"/></svg><svg class="caret" viewBox="0 0 24 24"><use href="#i-chevron-down"/></svg></div>
<div class="chip" id="chipDate" onclick="openDateFilter(this)"><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg>出库时间 <span class="cv" id="dateVal"></span></div> <div class="chip" id="chipDate" onclick="openDateFilter(this)"><svg viewBox="0 0 24 24"><use href="#i-ic48"/></svg>出库时间 <span class="cv" id="dateVal"></span></div>
<div class="btn soft sm" id="advBtn" onclick="openAdvSearch()"><svg viewBox="0 0 24 24"><use href="#i-ic21"/></svg>详细搜索<span class="cv" id="advCount"></span></div> <div class="btn soft sm" id="advBtn" onclick="openAdvSearch()"><svg viewBox="0 0 24 24"><use href="#i-ic21"/></svg>详细搜索<span class="cv" id="advCount"></span></div>
<div class="sp"></div> <div class="sp"></div>
@@ -143,9 +148,14 @@ const ORDERS=[
{no:'CK-20260614-002',date:'2026-06-14',party:'国际会展中心',wh:'主仓',count:1,amount:8400,status:'草稿',op:'张主管',auditor:'',lines:[{name:'西凤酒 旗舰版',qty:24,price:350}]}, {no:'CK-20260614-002',date:'2026-06-14',party:'国际会展中心',wh:'主仓',count:1,amount:8400,status:'草稿',op:'张主管',auditor:'',lines:[{name:'西凤酒 旗舰版',qty:24,price:350}]},
{no:'CK-20260613-005',date:'2026-06-13',party:'凯悦酒店宴会',wh:'名酒仓',count:1,amount:32400,status:'已审核',op:'王经理',auditor:'张主管',lines:[{name:'茅台 飞天 53°',qty:12,price:2700}]}, {no:'CK-20260613-005',date:'2026-06-13',party:'凯悦酒店宴会',wh:'名酒仓',count:1,amount:32400,status:'已审核',op:'王经理',auditor:'张主管',lines:[{name:'茅台 飞天 53°',qty:12,price:2700}]},
]; ];
const STATUS=['全部','草稿','待审核','已审核','已拒绝','部分退单','已退单']; // 状态筛选 2026-07-14 改多选:主状态(草稿/待审核/已审核/已拒绝)可多选,退单伪状态(部分退单/已退单)
// 与主状态互斥(选其一清另一侧)。默认「草稿+待审核」(D3 用户拍板)。
const MAIN_STATUS=['草稿','待审核','已审核','已拒绝'];
const RET_STATUS=['部分退单','已退单'];
const DEFAULT_STATUS=['草稿','待审核'];
const STATUS=['全部',...MAIN_STATUS,...RET_STATUS]; // 供详细搜索单选字段沿用
const COLS=[{key:'no',label:'单号'},{key:'party',label:'客户'},{key:'wh',label:'仓库'},{key:'amount',label:'合计金额',num:true},{key:'status',label:'状态'},{key:'op',label:'出库员'},{key:'auditor',label:'审核员'},{key:'act',label:'操作',fixed:true}]; const COLS=[{key:'no',label:'单号'},{key:'party',label:'客户'},{key:'wh',label:'仓库'},{key:'amount',label:'合计金额',num:true},{key:'status',label:'状态'},{key:'op',label:'出库员'},{key:'auditor',label:'审核员'},{key:'act',label:'操作',fixed:true}];
const state={q:'',party:'全部',wh:'全部',status:'全部',dateFrom:'',dateTo:'',page:1,perPage:10,orderNo:'',productInfo:'',batch:'',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部'}; const state={q:'',party:'全部',wh:'全部',statusSet:new Set(DEFAULT_STATUS),ret:'',dateFrom:'',dateTo:'',page:1,perPage:10,orderNo:'',productInfo:'',batch:'',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部'};
const PARTIES=['全部',...Array.from(new Set(ORDERS.map(o=>o.party)))]; const PARTIES=['全部',...Array.from(new Set(ORDERS.map(o=>o.party)))];
const WAREHOUSES=['全部',...Array.from(new Set(ORDERS.map(o=>o.wh)))]; const WAREHOUSES=['全部',...Array.from(new Set(ORDERS.map(o=>o.wh)))];
@@ -158,7 +168,8 @@ const STICON={'草稿':'i-ic06','待审核':'i-ic04','已审核':'i-check','已
function stBadge(v){ return `<span class="badge bi b-${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg>${v}</span>`; } function stBadge(v){ return `<span class="badge bi b-${v}"><svg class="ic"><use href="#${STICON[v]}"/></svg>${v}</span>`; }
function numf(n){ return (Number(n)||0).toLocaleString(); } /* 明细行内数字:去¥,缩一号(2026-07-03 */ function numf(n){ return (Number(n)||0).toLocaleString(); } /* 明细行内数字:去¥,缩一号(2026-07-03 */
function filtered(){ return ORDERS.filter(o=>{ function filtered(){ return ORDERS.filter(o=>{
if(state.status!=='全部'&&o.status!==state.status&&o.ret!==state.status)return false; if(state.ret){ if(o.ret!==state.ret)return false; }
else if(state.statusSet.size&&!state.statusSet.has(o.status))return false;
if(state.party!=='全部'&&o.party!==state.party)return false; if(state.party!=='全部'&&o.party!==state.party)return false;
if(state.wh!=='全部'&&o.wh!==state.wh)return false; if(state.wh!=='全部'&&o.wh!==state.wh)return false;
if(state.dateFrom&&o.date<state.dateFrom)return false; if(state.dateFrom&&o.date<state.dateFrom)return false;
@@ -175,11 +186,20 @@ function filtered(){ return ORDERS.filter(o=>{
if(state.category&&state.category!=='全部'&&!L.some(l=>(PINFO[l.name]||{}).cat===state.category))return false; if(state.category&&state.category!=='全部'&&!L.some(l=>(PINFO[l.name]||{}).cat===state.category))return false;
if(state.batch){const b=state.batch.toLowerCase(); if(!L.some(l=>{const pi=PINFO[l.name]||{}; return (pi.batch||'').toLowerCase().includes(b)||(pi.code||'').toLowerCase().includes(b);}))return false;} if(state.batch){const b=state.batch.toLowerCase(); if(!L.some(l=>{const pi=PINFO[l.name]||{}; return (pi.batch||'').toLowerCase().includes(b)||(pi.code||'').toLowerCase().includes(b);}))return false;}
return true; }); } return true; }); }
// 状态 chip 摘要(镜像 inventory.html 多选 chip):无=空、1 项=值本身、多项=「N 项」;退单态优先展示。
function statusChipLabel(){
if(state.ret) return state.ret;
if(!state.statusSet.size) return '';
if(state.statusSet.size===1) return [...state.statusSet][0];
return state.statusSet.size+' 项';
}
// 状态是否为默认态(草稿+待审核、无退单)——仅用于「重置」按钮高亮判断,避免默认态常年高亮。
function isDefaultStatus(){ return !state.ret && state.statusSet.size===2 && state.statusSet.has('草稿') && state.statusSet.has('待审核'); }
function syncChips(){ function syncChips(){
const set=(id,chipId,val)=>{ document.getElementById(id).textContent=val||''; document.getElementById(chipId).classList.toggle('on',!!val); }; const set=(id,chipId,val)=>{ document.getElementById(id).textContent=val||''; document.getElementById(chipId).classList.toggle('on',!!val); };
set('partyVal','chipParty',state.party==='全部'?'':state.party); set('partyVal','chipParty',state.party==='全部'?'':state.party);
set('whVal','chipWh',state.wh==='全部'?'':state.wh); set('whVal','chipWh',state.wh==='全部'?'':state.wh);
set('statusVal','chipStatus',state.status==='全部'?'':state.status); set('statusVal','chipStatus',statusChipLabel());
set('dateVal','chipDate',state.dateLabel||''); set('dateVal','chipDate',state.dateLabel||'');
const advCount=[state.orderNo,state.productInfo,state.batch, const advCount=[state.orderNo,state.productInfo,state.batch,
(state.series&&state.series!=='全部')?1:0, (state.series&&state.series!=='全部')?1:0,
@@ -191,13 +211,13 @@ function syncChips(){
advBtnEl.classList.toggle('primary',advCount>0); advBtnEl.classList.toggle('primary',advCount>0);
advBtnEl.classList.toggle('soft',advCount===0); advBtnEl.classList.toggle('soft',advCount===0);
document.getElementById('advCount').textContent=advCount?' · '+advCount:''; document.getElementById('advCount').textContent=advCount?' · '+advCount:'';
const any=state.q||state.party!=='全部'||state.wh!=='全部'||state.status!=='全部'||state.dateFrom||state.dateTo||advCount>0; const any=state.q||state.party!=='全部'||state.wh!=='全部'||!isDefaultStatus()||state.dateFrom||state.dateTo||advCount>0;
document.getElementById('resetBtn').classList.toggle('primary',!!any); document.getElementById('resetBtn').classList.toggle('primary',!!any);
} }
function render(){ function render(){
syncChips(); syncChips();
const cols=COLS; const cols=COLS;
document.getElementById('thead').innerHTML='<tr>'+cols.map(c=>{ const cls=(c.num?'num ':'')+(c.key==='act'?'act ':''); if(c.filter){const on=state.status!=='全部'; return `<th class="${cls}"><span class="fh ${on?'filtered':''}" onclick="openFilter(this)">${c.label} <svg class="funnel" viewBox="0 0 24 24"><use href="#i-ic22"/></svg></span></th>`;} return `<th class="${cls}">${c.label}</th>`; }).join('')+'</tr>'; document.getElementById('thead').innerHTML='<tr>'+cols.map(c=>{ const cls=(c.num?'num ':'')+(c.key==='act'?'act ':''); if(c.filter){const on=!isDefaultStatus(); return `<th class="${cls}"><span class="fh ${on?'filtered':''}" onclick="openFilter(this)">${c.label} <svg class="funnel" viewBox="0 0 24 24"><use href="#i-ic22"/></svg></span></th>`;} return `<th class="${cls}">${c.label}</th>`; }).join('')+'</tr>';
const rows=filtered(); const start=(state.page-1)*state.perPage; const pageRows=rows.slice(start,start+state.perPage); const tb=document.getElementById('tbody'); const rows=filtered(); const start=(state.page-1)*state.perPage; const pageRows=rows.slice(start,start+state.perPage); const tb=document.getElementById('tbody');
if(rows.length===0){ tb.innerHTML=`<tr><td colspan="${cols.length}"><div class="empty">没有匹配的出库单 · 试试调整筛选或搜索</div></td></tr>`; } if(rows.length===0){ tb.innerHTML=`<tr><td colspan="${cols.length}"><div class="empty">没有匹配的出库单 · 试试调整筛选或搜索</div></td></tr>`; }
else { tb.innerHTML=pageRows.map(o=>{ const idx=ORDERS.indexOf(o); return '<tr class="click" onclick="openDetail('+idx+')">'+cols.map(c=>{ if(c.key==='no')return `<td><span class="pcode" style="font-size:var(--fs-body)">${o.no}</span></td>`; if(c.key==='party')return `<td class="pname">${o.party}</td>`; if(c.key==='wh')return `<td>${o.wh}</td>`; if(c.key==='amount')return `<td class="num">${money(o.amount)}</td>`; if(c.key==='status')return `<td>${stBadge(o.status)}${o.ret?' '+stBadge(o.ret):''}</td>`; if(c.key==='op')return `<td>${o.op}</td>`; if(c.key==='auditor')return `<td>${o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>'}</td>`; if(c.key==='act')return `<td class="act"><div class="row-act" onclick="event.stopPropagation()"><svg viewBox="0 0 24 24" onclick="openDetail(${idx})"><use href="#i-eye"/></svg></div></td>`; return `<td>${o[c.key]}</td>`; }).join('')+'</tr>'; }).join(''); } else { tb.innerHTML=pageRows.map(o=>{ const idx=ORDERS.indexOf(o); return '<tr class="click" onclick="openDetail('+idx+')">'+cols.map(c=>{ if(c.key==='no')return `<td><span class="pcode" style="font-size:var(--fs-body)">${o.no}</span></td>`; if(c.key==='party')return `<td class="pname">${o.party}</td>`; if(c.key==='wh')return `<td>${o.wh}</td>`; if(c.key==='amount')return `<td class="num">${money(o.amount)}</td>`; if(c.key==='status')return `<td>${stBadge(o.status)}${o.ret?' '+stBadge(o.ret):''}</td>`; if(c.key==='op')return `<td>${o.op}</td>`; if(c.key==='auditor')return `<td>${o.auditor?o.auditor:'<span style="color:var(--faint)">—</span>'}</td>`; if(c.key==='act')return `<td class="act"><div class="row-act" onclick="event.stopPropagation()"><svg viewBox="0 0 24 24" onclick="openDetail(${idx})"><use href="#i-eye"/></svg></div></td>`; return `<td>${o[c.key]}</td>`; }).join('')+'</tr>'; }).join(''); }
@@ -209,10 +229,32 @@ function go(p){ const pages=Math.max(1,Math.ceil(filtered().length/state.perPage
const PAGE_SIZES=[10,20,50,100]; const PAGE_SIZES=[10,20,50,100];
function openPageSize(e){ openMenu(e.currentTarget, PAGE_SIZES.map(n=>({v:String(n),label:String(n),sel:n===state.perPage})), v=>{ closeMenus(); setPerPage(v); }, 'pgsize-menu', 88); } function openPageSize(e){ openMenu(e.currentTarget, PAGE_SIZES.map(n=>({v:String(n),label:String(n),sel:n===state.perPage})), v=>{ closeMenus(); setPerPage(v); }, 'pgsize-menu', 88); }
function setPerPage(v){ state.perPage=+v; state.page=1; const el=document.getElementById('pageSizeVal'); if(el)el.textContent=v; render(); } function setPerPage(v){ state.perPage=+v; state.page=1; const el=document.getElementById('pageSizeVal'); if(el)el.textContent=v; render(); }
function filterStatus(s){ state.status=s; state.page=1; render(); } // KPI 卡点击 = 覆盖为该单值(D5,非 toggle 集合成员)。
function clearFilters(){ state.q='';state.party='全部';state.wh='全部';state.status='全部';state.dateFrom='';state.dateTo='';state.dateLabel='';state.datePreset='all';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';state.page=1; document.getElementById('searchInput').value=''; render(); } function filterStatus(s){
if(RET_STATUS.includes(s)){ state.ret=s; state.statusSet=new Set(); }
else { state.ret=''; state.statusSet=new Set([s]); }
state.page=1; render();
}
// 状态 chip 的 × 单清:回到默认「草稿+待审核」,不是回「全部」(D3)。
function clearStatus(e){ e.stopPropagation(); state.statusSet=new Set(DEFAULT_STATUS); state.ret=''; state.page=1; render(); }
function clearFilters(){ state.q='';state.party='全部';state.wh='全部';state.statusSet=new Set(DEFAULT_STATUS);state.ret='';state.dateFrom='';state.dateTo='';state.dateLabel='';state.datePreset='all';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';state.page=1; document.getElementById('searchInput').value=''; render(); }
function openStatusFilter(t){ openMenu(t,STATUS.map(v=>({v,label:v,sel:v===state.status})),v=>{ state.status=v; state.page=1; closeMenus(); render(); }); } // 状态多选菜单:主状态 checkbox 多选 + 退单伪状态互斥(选其一清主状态多选,反之亦然);
// 菜单勾选后不关闭(镜像 inventory.html openDimFilter 的重开手法)。
function openStatusFilter(t){
const opts=[...MAIN_STATUS.map(v=>({v,label:v,sel:state.statusSet.has(v)})),
...RET_STATUS.map(v=>({v,label:v,sel:state.ret===v}))];
openMenu(t,opts,v=>{
if(RET_STATUS.includes(v)){
state.ret = state.ret===v ? '' : v;
state.statusSet=new Set();
} else {
state.ret='';
state.statusSet.has(v)?state.statusSet.delete(v):state.statusSet.add(v);
}
state.page=1; render(); openStatusFilter(t);
});
}
function openPartyFilter(t){ openSearchMenu(t,PARTIES.map(v=>({v,label:v,sel:v===state.party})),v=>{ state.party=v; state.page=1; closeMenus(); render(); },{placeholder:'搜索客户…'}); } function openPartyFilter(t){ openSearchMenu(t,PARTIES.map(v=>({v,label:v,sel:v===state.party})),v=>{ state.party=v; state.page=1; closeMenus(); render(); },{placeholder:'搜索客户…'}); }
function openWhFilter(t){ openMenu(t,WAREHOUSES.map(v=>({v,label:v,sel:v===state.wh})),v=>{ state.wh=v; state.page=1; closeMenus(); render(); }); } function openWhFilter(t){ openMenu(t,WAREHOUSES.map(v=>({v,label:v,sel:v===state.wh})),v=>{ state.wh=v; state.page=1; closeMenus(); render(); }); }
const TODAY='2026-06-22'; const TODAY='2026-06-22';
@@ -259,8 +301,14 @@ const OP_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.op)))];
const REVIEWER_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.auditor).filter(Boolean)))]; const REVIEWER_OPTS=['全部',...Array.from(new Set(ORDERS.map(o=>o.auditor).filter(Boolean)))];
let adv={}; let adv={};
function advSet(id,val){ const el=document.getElementById(id); if(el)el.value=(!val||val==='全部')?'':val; } function advSet(id,val){ const el=document.getElementById(id); if(el)el.value=(!val||val==='全部')?'':val; }
// 详细搜索的「状态」字段仍是单选(原型/Flutter 均未扩展为多选):多选态无法单值呈现时归「全部」。
function currentStatusForAdv(){
if(state.ret) return state.ret;
if(state.statusSet.size===1) return [...state.statusSet][0];
return '全部';
}
function openAdvSearch(){ function openAdvSearch(){
adv={party:state.party,series:state.series,spec:state.spec,category:state.category,op:state.op,reviewer:state.reviewer,status:state.status,dateFrom:state.dateFrom||'',dateTo:state.dateTo||''}; adv={party:state.party,series:state.series,spec:state.spec,category:state.category,op:state.op,reviewer:state.reviewer,status:currentStatusForAdv(),dateFrom:state.dateFrom||'',dateTo:state.dateTo||''};
advSet('advParty',adv.party); advSet('advSeries',adv.series); advSet('advSpec',adv.spec); advSet('advCategory',adv.category); advSet('advParty',adv.party); advSet('advSeries',adv.series); advSet('advSpec',adv.spec); advSet('advCategory',adv.category);
advSet('advOp',adv.op); advSet('advReviewer',adv.reviewer); advSet('advStatus',adv.status); advSet('advOp',adv.op); advSet('advReviewer',adv.reviewer); advSet('advStatus',adv.status);
document.getElementById('advNo').value=state.orderNo||''; document.getElementById('advNo').value=state.orderNo||'';
@@ -285,7 +333,10 @@ function applyAdv(){
state.productInfo=document.getElementById('advProduct').value.trim(); state.productInfo=document.getElementById('advProduct').value.trim();
state.batch=document.getElementById('advBatch').value.trim(); state.batch=document.getElementById('advBatch').value.trim();
state.party=adv.party; state.series=adv.series; state.spec=adv.spec; state.category=adv.category; state.party=adv.party; state.series=adv.series; state.spec=adv.spec; state.category=adv.category;
state.op=adv.op; state.reviewer=adv.reviewer; state.status=adv.status; state.op=adv.op; state.reviewer=adv.reviewer;
if(adv.status==='全部'){ state.statusSet=new Set(); state.ret=''; }
else if(RET_STATUS.includes(adv.status)){ state.ret=adv.status; state.statusSet=new Set(); }
else { state.statusSet=new Set([adv.status]); state.ret=''; }
state.dateFrom=adv.dateFrom; state.dateTo=adv.dateTo; state.dateFrom=adv.dateFrom; state.dateTo=adv.dateTo;
state.dateLabel=adv.dateFrom?adv.dateFrom+' ~ '+adv.dateTo:''; state.datePreset=adv.dateFrom?'custom':'all'; state.dateLabel=adv.dateFrom?adv.dateFrom+' ~ '+adv.dateTo:''; state.datePreset=adv.dateFrom?'custom':'all';
state.page=1; closeOverlay('ovAdv'); render(); state.page=1; closeOverlay('ovAdv'); render();
@@ -293,7 +344,7 @@ function applyAdv(){
function resetAdv(){ function resetAdv(){
adv={party:'全部',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部',status:'全部',dateFrom:'',dateTo:''}; adv={party:'全部',series:'全部',spec:'全部',category:'全部',op:'全部',reviewer:'全部',status:'全部',dateFrom:'',dateTo:''};
['advNo','advProduct','advBatch','advParty','advSeries','advSpec','advCategory','advOp','advReviewer','advStatus','advDate'].forEach(id=>{const el=document.getElementById(id); if(el)el.value='';}); ['advNo','advProduct','advBatch','advParty','advSeries','advSpec','advCategory','advOp','advReviewer','advStatus','advDate'].forEach(id=>{const el=document.getElementById(id); if(el)el.value='';});
state.party='全部';state.status='全部';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部'; state.party='全部';state.statusSet=new Set();state.ret='';state.orderNo='';state.productInfo='';state.batch='';state.series='全部';state.spec='全部';state.category='全部';state.op='全部';state.reviewer='全部';
applyDatePreset('all'); state.page=1; render(); applyDatePreset('all'); state.page=1; render();
} }
function auditBtns(o,idx){ function auditBtns(o,idx){