// ignore_for_file: unnecessary_set_literal import 'package:flutter/material.dart'; import 'package:wisdom_cli/src/assets.gen.dart'; import 'package:wisdom_cli/wisdom_cli.dart'; ///Picker抽象类 ///[WPicker], ///[WPickerUtil.infinity], ///[WPickerUtil.multiple], ///[WPickerUtil.single]中的[getData]返回的[List.item]类,应该要实现该抽象类 /// abstract class WPickerEntity { ///显示的名字 String get selectName; ///唯一值 Object get selectUnique; ///是否禁用 bool get selectDisabled; } /// ///通用快捷的构造[WPickerEntity], ///只需要传入一个[name], ///[unique]为次要的。不传也可以 class SinglePickerEntity implements WPickerEntity { final String? name; final Object? unique; final bool? disabled; final List? child; final List? cascade; SinglePickerEntity({ this.name, this.unique, this.disabled = false, this.child, this.cascade, }); @override String get selectName => name ?? ''; @override Object get selectUnique => unique ?? ''; @override bool get selectDisabled => disabled ?? false; List? get childList => child ?? []; List get cascadeList => cascade ?? []; ///Map快速构造[List] /// ///```json ///{ ///unique1:name1, ///unique2:name2, ///unique3:name3, ///unique4:name4, ///unique5:name5, ///unique6:name6, ///.... ///} ///``` static Future> map2entity( Map data) async { final keys = data.keys; final list = keys .map((key) => SinglePickerEntity(name: data[key]!, unique: key)) .toList(); return list; } ///Map快速构造[List] /// ///此构造方法[unique]也会使用[name],请确保数组无重复项 ///```json ///[name1,name2,name3,...] ///``` static Future> list2entity(List data) async { final list = data.map((e) => SinglePickerEntity(name: e)).toList(); return list; } ///Map快速构造[List] /// list to entity /// @data 列表数据 /// @label 标题字段 /// @value 值字段 static Future> listMap2entity( List data, label, value, {List? enableValue}) async { final list = data .map( (e) => SinglePickerEntity( name: e['$label'], unique: e['$value'], disabled: (enableValue != null && enableValue.length > 0) ? enableValue.contains(e['$value']) : false, ), ) .toList(); return list; } } ///选择组件、非常基础。 /// ///[getData]异步返回一个[List], ///列表项[List.item]应该是一个[WPickerEntity]的实现类 /// ///具体使用可参[底部弹出层封装] ///[WPickerUtil.single]、 ///[WPickerUtil.multiple]、 ///[WPickerUtil.infinity] /// /// class WPicker extends StatefulWidget { WPicker({ Key? key, this.getData, this.multiple = false, this.dense = false, this.initialValue = const [], this.listData, // this.enableValue = const [], this.onChanged, this.isSearch = false, }) : assert(multiple != null), assert(dense != null), assert(initialValue != null), assert(initialValue!.length <= 1 || multiple == true), super(key: key); ///是否多选 final bool? multiple; final bool? dense; /// 是否可搜索 final bool? isSearch; ///列表项[List.item]应该是一个[WPickerEntity]的实现类 final WSinglePickerGetData? getData; final List? listData; ///为了单选多选的通用性,[initialValue]是一个List。 ///当单选时,即[multiple==false],应满足[initialValue.length<=1] final List? initialValue; // final List enableValue; ///选项发生改变时触发。 ///[isTap==true]时,说明触发该函数的是由用户点击引起的, ///[isTap==false]时,说明触发该函数可能是由初始化,或其他组件内部原因引起的。 final void Function(List? value, bool isTap)? onChanged; @override _WPickerState createState() => _WPickerState(); } class _WPickerState extends State> { bool loading = false; List? list = []; List? initList = []; List? value; void getData() async { if (widget.getData != null) { loading = true; try { initList = (await widget.getData!()); list = initList; } finally { loading = false; } setState(() {}); } else { loading = true; try { initList = widget.listData; list = initList; } finally { loading = false; } setState(() {}); } } @override void initState() { super.initState(); value = List.of(widget.initialValue ?? []); widget.onChanged!(value, false); getData(); } ///是否已经选中? ///判断唯一值 bool isSelected(T v) { return value!.indexWhere((e) => e.selectUnique == v.selectUnique) != -1; } ///点击事件 ///会触发widget.onChanged(selects, true); void ontap(T v) { if (!(v.selectDisabled)) { if (!widget.multiple!) { value = [v]; } else { final exit = isSelected(v); if (exit) { value!.removeWhere((e) => e.selectUnique == v.selectUnique); } else { value!.add(v); } } setState(() {}); if (widget.onChanged != null) { widget.onChanged!(value, true); } } } void onInput(v) { List cList = (initList!.map((e) => e.selectName.contains(v) ? e : null)).toList(); List tempList = []; cList.forEach((element) { if (element != null) { tempList.add(element); } }); list = tempList; setState(() {}); } /// 为解决flutter截断不完整 String breakWord(String text) { if (text.isEmpty) { return text; } String breakWord = ' '; text.runes.forEach((element) { breakWord += String.fromCharCode(element); breakWord += '\u200B'; }); return breakWord; } @override Widget build(BuildContext context) { if (loading) return Center(child: WLoading()); final colorScheme = WTheme.of(context).colorScheme; return Stack( children: [ if (list == null || list!.length == 0 || list?.isEmpty == true) ...[ WEmptyWidget( margin: EdgeInsets.only( top: widget.isSearch == true ? 59.pt : 15.pt, ), text: WisText('暂无数据'), ), ] else ...[ ListView( padding: EdgeInsets.only( top: widget.isSearch == true ? 49.pt : 0, ), children: list!.map( (value) { return Wisdom( width: MediaQuery.of(context).size.width, margin: EdgeInsets.symmetric(horizontal: 15.pt), border: Border(bottom: Divider.createBorderSide(context)), child: ListTile( onTap: () => ontap(value), dense: widget.dense, contentPadding: EdgeInsets.zero, selected: isSelected(value), enabled: !(value.selectDisabled), title: Wisdom.row( width: MediaQuery.of(context).size.width, // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // 增加勾选按钮样式 if (isSelected(value)) ...[ Wisdom( padding: EdgeInsets.only(right: 10.pt), child: widget.multiple == true ? Image( image: AssetList.$fxxz_png_image, height: 16.pt) : Image( image: AssetList.$danxuan_png_image, height: 16.pt), ), WisText( '${breakWord(value.selectName)}', color: colorScheme.primary, maxLines: 1, overflow: TextOverflow.ellipsis, ).asExpanded(), ] else ...[ Wisdom( padding: EdgeInsets.only(right: 10.pt), child: widget.multiple == true ? Image( image: AssetList.$dx_png_image, height: 16.pt) : Image( image: AssetList.$danxuanyuan_png_image, height: 16.pt), ), WisText( '${breakWord(value.selectName)}', maxLines: 1, overflow: TextOverflow.ellipsis, ).asExpanded(), ], ], ), ), ); }, ).toList(), ), ], if (widget.isSearch == true) ...[ Positioned( top: 0, left: 0, right: 0, child: WSearch( hintText: '输入关键词搜索', onSubmitted: (v) => onInput(v), onCancel: () => onInput(''), onChanged: (v) => onInput(v), moonlight: false, padding: EdgeInsets.symmetric(vertical: 10.pt), isDark: true, backColor: Colors.white, ), ), ], ], ); } } class WTreePicker extends StatefulWidget { WTreePicker({ Key? key, required this.data, required this.labelname, required this.valuename, required this.childname, required this.firstName, required this.firstValue, this.multiple = false, this.dense = false, this.initialValue = const [], // this.enableValue = const [], this.onChanged, this.isSearch = false, }) : super(key: key); final String? labelname; final String? valuename; final String? childname; final String? firstName; final String? firstValue; ///是否多选 final bool? multiple; final bool? dense; /// 是否可搜索 final bool? isSearch; ///列表项[List.item]应该是一个[WPickerEntity]的实现类 final List? data; ///为了单选多选的通用性,[initialValue]是一个List。 ///当单选时,即[multiple==false],应满足[initialValue.length<=1] final List? initialValue; // final List enableValue; ///选项发生改变时触发。 ///[isTap==true]时,说明触发该函数的是由用户点击引起的, ///[isTap==false]时,说明触发该函数可能是由初始化,或其他组件内部原因引起的。 final void Function(List value, bool isTap)? onChanged; @override _WTreePickerState createState() => _WTreePickerState(); } class _WTreePickerState extends State> { bool? loading = false; List? list = []; var _fistValue; List? initList = []; List? secondList = []; String? _searchValue; List? value; void getData() async { if (widget.data != null) { loading = true; try { initList = widget.data; list = initList!; _fistValue = initList!.length > 0 ? initList![0] : null; secondList = _fistValue[widget.childname] ?? []; } finally { loading = false; } setState(() {}); } } @override void initState() { super.initState(); value = List.of(widget.initialValue ?? []); getData(); } ///是否已经选中? ///判断唯一值 bool isSelected(v) { return value! .indexWhere((e) => e[widget.valuename] == v[widget.valuename]) != -1; } ///点击事件 ///会触发widget.onChanged(selects, true); void ontap(v) { // if (!(v?.selectDisabled ?? false)) { if (widget.multiple == false) { value = [v]; } else { final exit = isSelected(v); if (exit) { value!.removeWhere((e) => e[widget.valuename] == v[widget.valuename]); } else { value!.add(v); } } setState(() {}); if (widget.onChanged != null) { widget.onChanged!(value!, true); } } onSelectFirst(v) { _fistValue = v; secondList = _fistValue[widget.childname] ?? []; setState(() {}); } void onInput(v) { _searchValue = v; if (v == null || v == '') { list = initList!; } else { List tempList = []; initList!.forEach((ele) => { ele[widget.childname].forEach((item) => { if (item[widget.labelname].contains(v)) { tempList.add(item), } }), }); list = tempList; } setState(() {}); } @override Widget build(BuildContext context) { final colorScheme = WTheme.of(context).colorScheme; if (loading == true) return Center(child: WLoading()); return Stack( children: [ Wisdom.row( color: Colors.white, mainAxisSize: MainAxisSize.min, children: [ if (_searchValue != null && _searchValue != '') ...[ if (list == null || list!.length == 0) ...[ Wisdom( width: MediaQuery.of(context).size.width, padding: EdgeInsets.only( top: widget.isSearch == true ? 49.pt : 0, ), child: Center( child: WEmptyWidget( text: WisText('搜索不到,空空如也~'), ), ), ), ] else ...[ SizedBox( height: double.infinity, width: MediaQuery.of(context).size.width, child: ListView( padding: EdgeInsets.only( top: widget.isSearch == true ? 49.pt : 0, ), children: list!.map( (value) { return Wisdom( margin: EdgeInsets.symmetric(horizontal: 15.pt), border: Border(bottom: Divider.createBorderSide(context)), child: ListTile( onTap: () => ontap(value), dense: widget.dense, contentPadding: EdgeInsets.zero, selected: false, enabled: true, title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ WisText( '${value[widget.labelname] ?? '--'}', maxLines: 1, overflow: TextOverflow.ellipsis, ).asExpanded(), // 增加勾选按钮样式 if (isSelected(value)) ...[ Wisdom( padding: EdgeInsets.only(left: 10.pt), child: Image( image: AssetList.$xzh_png_image, height: 10.pt, ), ), ], ], ), ), ); }, ).toList(), ), ).asExpanded(), ] ] else ...[ if (list != null && list!.length > 0) ...[ SizedBox( height: double.infinity, width: 130.pt, child: ListView( padding: EdgeInsets.only( top: widget.isSearch == true ? 49.pt : 0, ), children: list!.map( (value) { return Wisdom( margin: EdgeInsets.symmetric(horizontal: 15.pt), border: Border(bottom: Divider.createBorderSide(context)), child: ListTile( onTap: () => onSelectFirst(value), dense: widget.dense, contentPadding: EdgeInsets.zero, selected: _fistValue != null && _fistValue[widget.firstValue] == value[widget.firstValue], enabled: true, title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (_fistValue != null && _fistValue[widget.firstValue] == value[widget.firstValue]) ...[ Wisdom( color: colorScheme.primary, height: 14.pt, width: 2.pt, margin: EdgeInsets.only(right: 5.pt), ), ], WisText( '${value[widget.firstName] ?? '--'}', maxLines: 1, color: _fistValue != null && _fistValue[widget.firstValue] == value[widget.firstValue] ? colorScheme.primary : null, overflow: TextOverflow.ellipsis, ).asExpanded(), ], ), ), ); }, ).toList(), ), ), if (secondList != null && secondList!.length > 0) ...[ SizedBox( height: double.infinity, width: MediaQuery.of(context).size.width - 130.pt, child: ListView( padding: EdgeInsets.only( top: widget.isSearch == true ? 49.pt : 0, ), children: secondList!.map( (value) { return Wisdom( margin: EdgeInsets.only(right: 15.pt), border: Border( bottom: Divider.createBorderSide(context)), child: ListTile( onTap: () => ontap(value), dense: widget.dense, contentPadding: EdgeInsets.zero, selected: false, enabled: true, title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ WisText( '${value[widget.labelname] ?? '--'}', maxLines: 1, overflow: TextOverflow.ellipsis, ).asExpanded(), // 增加勾选按钮样式 if (isSelected(value)) ...[ Wisdom( padding: EdgeInsets.only(left: 10.pt), child: Image( image: AssetList.$xzh_png_image, height: 10.pt, ), ), ], ], ), ), ); }, ).toList(), ), ) ] else ...[ Wisdom( width: MediaQuery.of(context).size.width - 130.pt, padding: EdgeInsets.only( top: widget.isSearch == true ? 49.pt : 0, ), child: Center( child: WEmptyWidget( text: WisText('暂无数据'), ), ), ), ] ] else ...[ Wisdom( width: MediaQuery.of(context).size.width, padding: EdgeInsets.only( top: widget.isSearch == true ? 49.pt : 0, ), child: Center( child: WEmptyWidget( text: WisText('暂无数据'), ), ), ), ] ] ], ), if (widget.isSearch == true) ...[ Positioned( top: 0, left: 0, right: 0, child: WSearch( onCancel: () => onInput(null), hintText: '输入关键词搜索', onSubmitted: (v) => onInput(v), onChanged: (v) => onInput(v), moonlight: false, padding: EdgeInsets.symmetric(vertical: 10.pt), isDark: true, backColor: Colors.white, ), ), ], ], ); } }