feat: 电解控实时计算功能完成
This commit is contained in:
parent
49ddf62751
commit
10145b3af7
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
// Global config
|
// Global config
|
||||||
class GloablConfig {
|
class GloablConfig {
|
||||||
// static const BASE_URL = "http://10.0.2.2:8001/api/";
|
static const BASE_URL = "http://10.0.2.2:8001/api/";
|
||||||
// static const OSS_URL = "http://10.0.2.2:8001";
|
static const OSS_URL = "http://10.0.2.2:8001";
|
||||||
|
|
||||||
static const BASE_URL = "http://144.123.43.138:3001/api/";
|
// static const BASE_URL = "http://144.123.43.138:3001/api/";
|
||||||
static const OSS_URL = "http://144.123.43.138:3001";
|
// static const OSS_URL = "http://144.123.43.138:3001";
|
||||||
// static const BASE_URL = "http://192.168.60.220:8001/api/";
|
// static const BASE_URL = "http://192.168.60.220:8001/api/";
|
||||||
// static const OSS_URL = "http://192.168.60.220:8001";
|
// static const OSS_URL = "http://192.168.60.220:8001";
|
||||||
static const DOMAIN_NAME = "山矿通";
|
static const DOMAIN_NAME = "山矿通";
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||||
|
|
||||||
|
class BaseSearchMoreModel {
|
||||||
|
RxBool isExpanded = true.obs;
|
||||||
|
String? name;
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
|
import 'package:sk_base_mobile/models/base_search_more.model.dart';
|
||||||
|
|
||||||
mixin BaseSearchMore<T> {
|
mixin BaseSearchMoreController<T extends BaseSearchMoreModel> {
|
||||||
RxList<T> list = RxList([]);
|
RxList<T> list = RxList([]);
|
||||||
RxString searchKey = ''.obs;
|
RxString searchKey = ''.obs;
|
||||||
final searchBarTextConroller = TextEditingController();
|
final searchBarTextConroller = TextEditingController();
|
||||||
|
@ -10,6 +11,7 @@ mixin BaseSearchMore<T> {
|
||||||
int page = 1;
|
int page = 1;
|
||||||
int limit = 15;
|
int limit = 15;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
|
RxList<int> selectedIndex = RxList([]);
|
||||||
getData({bool isRefresh = false}) {}
|
getData({bool isRefresh = false}) {}
|
||||||
|
|
||||||
Future<void> onRefresh() async {
|
Future<void> onRefresh() async {
|
|
@ -1,22 +1,35 @@
|
||||||
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||||
|
import 'package:sk_base_mobile/models/base_search_more.model.dart';
|
||||||
|
|
||||||
class SaleQuotationModel {
|
class SaleQuotationModel extends BaseSearchMoreModel {
|
||||||
final String name;
|
final String name;
|
||||||
RxBool isExpanded = true.obs;
|
|
||||||
RxList<SaleQuotationItemModel> items;
|
RxList<SaleQuotationItemModel> items;
|
||||||
SaleQuotationModel({required this.name, required this.items});
|
SaleQuotationModel({required this.name, required this.items});
|
||||||
|
// 生成fromjson
|
||||||
|
SaleQuotationModel.fromJson(Map<String, dynamic> json)
|
||||||
|
: name = json['name'],
|
||||||
|
items = (json['items'] as List)
|
||||||
|
.map((e) => SaleQuotationItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
.obs;
|
||||||
|
// 生成tojson
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'name': name,
|
||||||
|
'items': items.map((e) => e.toJson()).toList(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class SaleQuotationItemModel {
|
class SaleQuotationItemModel extends BaseSearchMoreModel {
|
||||||
final String name;
|
final String name;
|
||||||
// 规格
|
// 规格
|
||||||
final String? spec;
|
final String? spec;
|
||||||
final String? unit;
|
final String? unit;
|
||||||
final String? remark;
|
final String? remark;
|
||||||
// 成本
|
// 成本
|
||||||
final double cost;
|
int cost;
|
||||||
final int quantity;
|
int quantity;
|
||||||
final double amount;
|
int amount;
|
||||||
|
|
||||||
SaleQuotationItemModel(
|
SaleQuotationItemModel(
|
||||||
{required this.name,
|
{required this.name,
|
||||||
this.spec,
|
this.spec,
|
||||||
|
@ -25,4 +38,22 @@ class SaleQuotationItemModel {
|
||||||
this.cost = 0,
|
this.cost = 0,
|
||||||
this.quantity = 0,
|
this.quantity = 0,
|
||||||
this.amount = 0});
|
this.amount = 0});
|
||||||
|
SaleQuotationItemModel.fromJson(Map<String, dynamic> json)
|
||||||
|
: name = json['name'],
|
||||||
|
spec = json['spec'],
|
||||||
|
unit = json['unit'],
|
||||||
|
remark = json['remark'],
|
||||||
|
cost = json['cost'],
|
||||||
|
quantity = json['quantity'],
|
||||||
|
amount = json['amount'];
|
||||||
|
// 生成tojson
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'name': name,
|
||||||
|
'spec': spec,
|
||||||
|
'unit': unit,
|
||||||
|
'remark': remark,
|
||||||
|
'cost': cost,
|
||||||
|
'quantity': quantity,
|
||||||
|
'amount': amount,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,8 +172,8 @@ class LoginScreen extends StatelessWidget {
|
||||||
|
|
||||||
Widget buildUserNameInput() {
|
Widget buildUserNameInput() {
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
// 英文数字键盘
|
// 纯英文数字键盘
|
||||||
obscureText: true,
|
keyboardType: TextInputType.emailAddress,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: Icon(
|
prefixIcon: Icon(
|
||||||
Icons.person_2_outlined,
|
Icons.person_2_outlined,
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/gradient_button.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/sk_appbar.dart';
|
||||||
|
|
||||||
|
class SaleQuotationEndDrawer extends StatelessWidget {
|
||||||
|
const SaleQuotationEndDrawer({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: SkAppbar(title: '工具栏', hideLeading: true),
|
||||||
|
body: buildBody(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildBody() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text('选择报价模板'),
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
color: AppTheme.dividerColor,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
GradientButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.save,
|
||||||
|
color: AppTheme.nearlyWhite,
|
||||||
|
size: ScreenAdaper.height(40),
|
||||||
|
),
|
||||||
|
buttonText: '保存为模板',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,15 +3,12 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
import 'package:sk_base_mobile/apis/index.dart';
|
|
||||||
import 'package:sk_base_mobile/app_theme.dart';
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
import 'package:sk_base_mobile/constants/bg_color.dart';
|
import 'package:sk_base_mobile/constants/bg_color.dart';
|
||||||
import 'package:sk_base_mobile/models/sale_quotation.model.dart';
|
import 'package:sk_base_mobile/models/sale_quotation.model.dart';
|
||||||
import 'package:sk_base_mobile/models/user_info.model.dart';
|
|
||||||
import 'package:sk_base_mobile/util/debouncer.dart';
|
import 'package:sk_base_mobile/util/debouncer.dart';
|
||||||
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
import 'package:sk_base_mobile/widgets/empty.dart';
|
import 'package:sk_base_mobile/widgets/empty.dart';
|
||||||
import 'package:sk_base_mobile/widgets/fade_in_cache_image.dart';
|
|
||||||
|
|
||||||
class SaleQuotationGroupSearch extends StatelessWidget {
|
class SaleQuotationGroupSearch extends StatelessWidget {
|
||||||
final Function(SaleQuotationModel)? onSelected;
|
final Function(SaleQuotationModel)? onSelected;
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:sk_base_mobile/models/base_search_more.dart';
|
import 'package:math_expressions/math_expressions.dart';
|
||||||
|
import 'package:sk_base_mobile/models/base_search_more_controller.dart';
|
||||||
import 'package:sk_base_mobile/models/sale_quotation.model.dart';
|
import 'package:sk_base_mobile/models/sale_quotation.model.dart';
|
||||||
import 'package:sk_base_mobile/screens/sale_quotation/components/sale_quotation_group_search.dart';
|
import 'package:sk_base_mobile/screens/sale_quotation/components/sale_quotation_group_search.dart';
|
||||||
|
import 'package:sk_base_mobile/services/storage.service.dart';
|
||||||
|
import 'package:sk_base_mobile/util/snack_bar.util.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/core/sk_muti_search_more.dart';
|
||||||
import 'package:sk_base_mobile/widgets/core/sk_single_search_more.dart';
|
import 'package:sk_base_mobile/widgets/core/sk_single_search_more.dart';
|
||||||
import 'package:sk_base_mobile/util/modal.util.dart';
|
import 'package:sk_base_mobile/util/modal.util.dart';
|
||||||
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
@ -11,6 +18,7 @@ import 'package:pinyin/pinyin.dart';
|
||||||
class SaleQuotationController extends GetxController {
|
class SaleQuotationController extends GetxController {
|
||||||
static SaleQuotationController get to => Get.find();
|
static SaleQuotationController get to => Get.find();
|
||||||
final RxList editingcell = RxList([null, null, null]);
|
final RxList editingcell = RxList([null, null, null]);
|
||||||
|
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
RxList<SaleQuotationItemModel> products = RxList([
|
RxList<SaleQuotationItemModel> products = RxList([
|
||||||
SaleQuotationItemModel(name: '矿用本安型支架控制器', unit: '台', spec: 'ZDYZ-Z'),
|
SaleQuotationItemModel(name: '矿用本安型支架控制器', unit: '台', spec: 'ZDYZ-Z'),
|
||||||
SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器'),
|
SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器'),
|
||||||
|
@ -34,90 +42,178 @@ class SaleQuotationController extends GetxController {
|
||||||
SaleQuotationItemModel(name: '各类安装附件'),
|
SaleQuotationItemModel(name: '各类安装附件'),
|
||||||
]);
|
]);
|
||||||
RxList<SaleQuotationModel> groups = RxList<SaleQuotationModel>([]);
|
RxList<SaleQuotationModel> groups = RxList<SaleQuotationModel>([]);
|
||||||
|
RxInt totalCost = RxInt(0);
|
||||||
|
RxDouble totalPrice = RxDouble(0.0);
|
||||||
|
RxBool isFormulaEditing = false.obs;
|
||||||
|
RxString formula = '成本 * 1.3 / 0.864'.obs;
|
||||||
@override
|
@override
|
||||||
void onReady() {
|
void onReady() {
|
||||||
|
init();
|
||||||
super.onReady();
|
super.onReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addGroup() async {
|
Future<void> init() async {
|
||||||
|
String? salesQuotation = StorageService.to.getString('salesQuotation');
|
||||||
|
if (salesQuotation != null) {
|
||||||
|
groups.assignAll((jsonDecode(salesQuotation) as List)
|
||||||
|
.map((e) => SaleQuotationModel.fromJson(e))
|
||||||
|
.toList());
|
||||||
|
calculateTotal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void calculateTotal({String? newFormula}) {
|
||||||
|
//计算groups中所有items中的amout总和
|
||||||
|
totalCost.value = groups.fold<int>(0, (previousValue, element) {
|
||||||
|
return previousValue +
|
||||||
|
element.items.fold<int>(0, (previousValue, element) {
|
||||||
|
return previousValue + element.amount;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Parser p = Parser();
|
||||||
|
Expression exp = p.parse(newFormula ?? formula.value);
|
||||||
|
Variable x = Variable('成本');
|
||||||
|
ContextModel cm = ContextModel()..bindVariable(x, Number(totalCost.value));
|
||||||
|
double eval = exp.evaluate(EvaluationType.REAL, cm);
|
||||||
|
totalPrice.value = eval.toPrecision(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 实时计算数量,单价,总价之间的关系
|
||||||
|
SaleQuotationItemModel calculateRow(
|
||||||
|
SaleQuotationItemModel data, String changedField) {
|
||||||
|
Decimal quantity = Decimal.fromInt(0);
|
||||||
|
Decimal cost = Decimal.fromInt(0);
|
||||||
|
Decimal amount = Decimal.fromInt(0);
|
||||||
|
if (data.quantity != 0) {
|
||||||
|
quantity = Decimal.parse('${data.quantity}');
|
||||||
|
}
|
||||||
|
if (data.cost != 0) {
|
||||||
|
cost = Decimal.parse('${data.cost}');
|
||||||
|
}
|
||||||
|
if (data.amount != 0) {
|
||||||
|
amount = Decimal.parse('${data.amount}');
|
||||||
|
}
|
||||||
|
// 入库一般是先输入单价和数量,然后计算单价
|
||||||
|
if (changedField != 'amount') {
|
||||||
|
Decimal result = cost * quantity;
|
||||||
|
data.amount = result != Decimal.zero ? result.toBigInt().toInt() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedField == 'amount' && quantity != Decimal.zero) {
|
||||||
|
Decimal result =
|
||||||
|
(amount / quantity).toDecimal(scaleOnInfinitePrecision: 10);
|
||||||
|
data.cost = result != Decimal.zero ? result.toBigInt().toInt() : 0;
|
||||||
|
} else if (changedField != 'amount') {
|
||||||
|
Decimal result = (cost * quantity);
|
||||||
|
data.amount = result != Decimal.zero ? result.toBigInt().toInt() : 0;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveChanges(int groupIndex, int rowIndex, SaleQuotationItemModel data,
|
||||||
|
String changedField) {
|
||||||
|
data = calculateRow(data, changedField);
|
||||||
|
groups[groupIndex].items[rowIndex] = data;
|
||||||
|
calculateTotal();
|
||||||
|
stopEditing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopEditing() {
|
||||||
|
editingcell.assignAll([null, null, null]);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> save() async {
|
||||||
|
await StorageService.to
|
||||||
|
.setString('salesQuotation', jsonEncode(groups.toJson()));
|
||||||
|
// SnackBarUtil().success('已保存到本地');
|
||||||
|
}
|
||||||
|
|
||||||
|
void addGroup() async {
|
||||||
final controller = Get.put(GroupSearchMoreController());
|
final controller = Get.put(GroupSearchMoreController());
|
||||||
// 选择组件 选择分组
|
// 选择组件 选择分组
|
||||||
ModalUtil.showGeneralDialog(
|
ModalUtil.showGeneralDialog(
|
||||||
content: SkSingleSearchMore<SaleQuotationModel>(
|
content: SkMutilSearchMore<SaleQuotationModel>(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
enablePullUp: false,
|
enablePullUp: false,
|
||||||
enablePullDown: true,
|
enablePullDown: true,
|
||||||
itemBuilder: (_, index) {
|
onOk: (List<int> indexes) {
|
||||||
return InkWell(
|
groups.addAll(controller.list.where((element) {
|
||||||
onTap: () {
|
return indexes.contains(controller.list.indexOf(element));
|
||||||
Get.back();
|
}));
|
||||||
groups.add(controller.list[index]);
|
save();
|
||||||
},
|
},
|
||||||
child: Container(
|
leadingBuilder: (index) {
|
||||||
padding: EdgeInsets.symmetric(
|
return Container(
|
||||||
horizontal: ScreenAdaper.width(5),
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: ScreenAdaper.height(10)),
|
horizontal: ScreenAdaper.width(5),
|
||||||
child: Row(
|
vertical: ScreenAdaper.height(10)),
|
||||||
children: [
|
child: Row(
|
||||||
Text(
|
children: [
|
||||||
controller.list[index].name,
|
Text(
|
||||||
style: TextStyle(fontSize: ScreenAdaper.height(25)),
|
controller.list[index].name,
|
||||||
),
|
style: TextStyle(fontSize: ScreenAdaper.height(30)),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
));
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
width: Get.width - ScreenAdaper.width(50))
|
width: Get.width - ScreenAdaper.width(50))
|
||||||
.then((value) => Get.delete<GroupSearchController>());
|
.then((value) => Get.delete<GroupSearchController>());
|
||||||
|
calculateTotal();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeGroup(int index) async {
|
void removeGroup(int index) {
|
||||||
groups.removeAt(index);
|
groups.removeAt(index);
|
||||||
|
calculateTotal();
|
||||||
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addItems(int groupIndex) async {
|
void addItems(int groupIndex) async {
|
||||||
final controller = Get.put(ItemSearchMoreController());
|
final controller = Get.put(ItemSearchMoreController());
|
||||||
// 选择产品
|
// 选择产品
|
||||||
ModalUtil.showGeneralDialog(
|
ModalUtil.showGeneralDialog(
|
||||||
content: SkSingleSearchMore<SaleQuotationItemModel>(
|
content: SkMutilSearchMore<SaleQuotationItemModel>(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
enablePullUp: false,
|
enablePullUp: false,
|
||||||
enablePullDown: true,
|
enablePullDown: true,
|
||||||
isDialog: true,
|
isDialog: true,
|
||||||
itemBuilder: (_, index) {
|
onOk: (List<int> indexes) {
|
||||||
return InkWell(
|
groups[groupIndex].items.addAll(products.where((element) {
|
||||||
onTap: () {
|
return indexes.contains(products.indexOf(element));
|
||||||
Get.back();
|
}));
|
||||||
groups[groupIndex].items.add(controller.list[index]);
|
save();
|
||||||
},
|
},
|
||||||
child: Container(
|
leadingBuilder: (index) {
|
||||||
padding: EdgeInsets.symmetric(
|
return Container(
|
||||||
horizontal: ScreenAdaper.width(5),
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: ScreenAdaper.height(10)),
|
horizontal: ScreenAdaper.width(5),
|
||||||
child: Row(
|
vertical: ScreenAdaper.height(10)),
|
||||||
children: [
|
child: Row(
|
||||||
Text(
|
children: [
|
||||||
controller.list[index].name,
|
Text(
|
||||||
style: TextStyle(fontSize: ScreenAdaper.height(25)),
|
controller.list[index].name,
|
||||||
),
|
style: TextStyle(fontSize: ScreenAdaper.height(25)),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
));
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
// onSelected: (SaleQuotationItemModel info) {
|
|
||||||
// Get.back();
|
|
||||||
// // groups.add(info);
|
|
||||||
// },
|
|
||||||
),
|
),
|
||||||
width: Get.width - ScreenAdaper.width(50))
|
width: Get.width - ScreenAdaper.width(50))
|
||||||
.then((value) => Get.delete<ItemSearchMoreController>());
|
.then((value) => Get.delete<ItemSearchMoreController>());
|
||||||
|
calculateTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeItem(int groupIndex, int rowIndex) {
|
||||||
|
groups[groupIndex].items.removeAt(rowIndex);
|
||||||
|
calculateTotal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupSearchMoreController extends GetxController
|
class GroupSearchMoreController extends GetxController
|
||||||
with BaseSearchMore<SaleQuotationModel> {
|
with BaseSearchMoreController<SaleQuotationModel> {
|
||||||
@override
|
@override
|
||||||
Future<List<SaleQuotationModel>> getData({bool isRefresh = false}) async {
|
Future<List<SaleQuotationModel>> getData({bool isRefresh = false}) async {
|
||||||
List<SaleQuotationModel> newList = [
|
List<SaleQuotationModel> newList = [
|
||||||
|
@ -139,7 +235,7 @@ class GroupSearchMoreController extends GetxController
|
||||||
}
|
}
|
||||||
|
|
||||||
class ItemSearchMoreController extends GetxController
|
class ItemSearchMoreController extends GetxController
|
||||||
with BaseSearchMore<SaleQuotationItemModel> {
|
with BaseSearchMoreController<SaleQuotationItemModel> {
|
||||||
@override
|
@override
|
||||||
Future<List<SaleQuotationItemModel>> getData({bool isRefresh = false}) async {
|
Future<List<SaleQuotationItemModel>> getData({bool isRefresh = false}) async {
|
||||||
List<SaleQuotationItemModel> newList = [
|
List<SaleQuotationItemModel> newList = [
|
||||||
|
|
|
@ -1,82 +1,228 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:date_format/date_format.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:sk_base_mobile/app_theme.dart';
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
import 'package:sk_base_mobile/models/sale_quotation.model.dart';
|
import 'package:sk_base_mobile/models/sale_quotation.model.dart';
|
||||||
|
import 'package:sk_base_mobile/screens/sale_quotation/components/sale_quotation_drawer.dart';
|
||||||
import 'package:sk_base_mobile/screens/sale_quotation/sale_quotation.controller.dart';
|
import 'package:sk_base_mobile/screens/sale_quotation/sale_quotation.controller.dart';
|
||||||
import 'package:sk_base_mobile/util/modal.util.dart';
|
import 'package:sk_base_mobile/util/modal.util.dart';
|
||||||
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
import 'package:sk_base_mobile/util/snack_bar.util.dart';
|
||||||
import 'package:sk_base_mobile/widgets/core/sk_number_input.dart';
|
import 'package:sk_base_mobile/widgets/core/sk_number_input.dart';
|
||||||
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
|
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
|
||||||
import 'package:sk_base_mobile/widgets/empty.dart';
|
import 'package:sk_base_mobile/widgets/empty.dart';
|
||||||
import 'package:sk_base_mobile/widgets/sk_appbar.dart';
|
import 'package:sk_base_mobile/widgets/sk_appbar.dart';
|
||||||
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
|
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
|
||||||
|
import 'package:math_expressions/math_expressions.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
class SaleQuotationPage extends StatelessWidget {
|
class SaleQuotationPage extends StatelessWidget {
|
||||||
SaleQuotationPage({super.key});
|
SaleQuotationPage({super.key});
|
||||||
final controller = Get.put(SaleQuotationController());
|
final controller = Get.put(SaleQuotationController());
|
||||||
final quantityWidth = 55.0;
|
final quantityWidth = 140.0;
|
||||||
final unitPriceWidth = 80.0;
|
final unitPriceWidth = 140.0;
|
||||||
final amountWidth = 85.0;
|
final amountWidth = 140.0;
|
||||||
final headerTitleStyle = TextStyle(
|
final headerTitleStyle = TextStyle(
|
||||||
fontSize: ScreenAdaper.height(30),
|
fontSize: ScreenAdaper.height(30),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: AppTheme.nearlyBlack);
|
color: AppTheme.nearlyBlack);
|
||||||
final headerBgcolor = Color.fromARGB(255, 238, 238, 238);
|
final headerBgcolor = const Color.fromARGB(255, 238, 238, 238);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
|
key: controller.scaffoldKey,
|
||||||
floatingActionButton: FloatingActionButton(
|
endDrawer: Drawer(
|
||||||
onPressed: () {
|
// 从右到左出现
|
||||||
controller.addGroup();
|
child: SaleQuotationEndDrawer(),
|
||||||
},
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
),
|
),
|
||||||
appBar: SkAppbar(
|
appBar: SkAppbar(
|
||||||
title: '报价计算',
|
title: '报价计算',
|
||||||
action: [
|
action: [
|
||||||
TextButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
controller.addGroup();
|
controller.scaffoldKey.currentState?.openEndDrawer();
|
||||||
},
|
},
|
||||||
child: Row(
|
icon: const Icon(Icons.more_horiz_outlined, color: AppTheme.white),
|
||||||
children: [
|
)
|
||||||
Icon(Icons.save, color: AppTheme.white),
|
|
||||||
SizedBox(
|
|
||||||
width: ScreenAdaper.width(10),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'保存',
|
|
||||||
style: TextStyle(
|
|
||||||
color: AppTheme.white,
|
|
||||||
fontSize: ScreenAdaper.height(25),
|
|
||||||
fontWeight: FontWeight.w600),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
))
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
bottom: ScreenAdaper.isLandspace() ? false : true,
|
bottom: ScreenAdaper.isLandspace() ? false : true,
|
||||||
child: Column(
|
child: Stack(children: [
|
||||||
children: [
|
Column(
|
||||||
builderHeader(),
|
children: [
|
||||||
Expanded(
|
builderHeader(),
|
||||||
child: Obx(() => controller.groups.isEmpty
|
Expanded(
|
||||||
? Empty(text: '请先添加分组')
|
child: Obx(() => controller.groups.isEmpty
|
||||||
: CustomScrollView(
|
? Empty(text: '请先添加分组')
|
||||||
slivers: controller.groups
|
: CustomScrollView(
|
||||||
.mapIndexed<Widget>(
|
slivers: controller.groups
|
||||||
(index, e) => buildBody(e, index))
|
.mapIndexed<Widget>(
|
||||||
.toList(),
|
(index, e) => buildBody(index))
|
||||||
))),
|
.toList(),
|
||||||
],
|
))),
|
||||||
|
SizedBox(
|
||||||
|
height: ScreenAdaper.height(100),
|
||||||
|
child: buildTotalCostRow(),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: ScreenAdaper.height(100),
|
||||||
|
child: buildTotalSalesPriceRow(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// Positioned(
|
||||||
|
// bottom: ScreenAdaper.height(10),
|
||||||
|
// right: ScreenAdaper.height(10),
|
||||||
|
// child: IconButton(
|
||||||
|
// padding: EdgeInsets.all(ScreenAdaper.height(20)),
|
||||||
|
// style: ButtonStyle(
|
||||||
|
// backgroundColor: MaterialStateProperty.all<Color>(
|
||||||
|
// AppTheme.primaryColorLight)),
|
||||||
|
// onPressed: () {
|
||||||
|
// controller.addGroup();
|
||||||
|
// },
|
||||||
|
// icon: Icon(
|
||||||
|
// Icons.add,
|
||||||
|
// size: ScreenAdaper.height(40),
|
||||||
|
// ),
|
||||||
|
// ))
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildTotalSalesPriceRow() {
|
||||||
|
final formulaController =
|
||||||
|
TextEditingController(text: controller.formula.value);
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: ScreenAdaper.width(20),
|
||||||
|
vertical: ScreenAdaper.height(10)),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(width: 1, color: Colors.grey[200]!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'合计',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.height(30), fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Obx(
|
||||||
|
() => Container(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding:
|
||||||
|
EdgeInsets.symmetric(horizontal: ScreenAdaper.width(10)),
|
||||||
|
height: ScreenAdaper.height(100),
|
||||||
|
width: ScreenAdaper.width(100),
|
||||||
|
child: controller.isFormulaEditing.value
|
||||||
|
? SkTextInput(
|
||||||
|
hint: '',
|
||||||
|
isDense: true,
|
||||||
|
autoFocus: true,
|
||||||
|
onFieldSubmitted: (value) {
|
||||||
|
controller.formula.value = value;
|
||||||
|
controller.isFormulaEditing.value = false;
|
||||||
|
},
|
||||||
|
validator: (String? value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Parser p = Parser();
|
||||||
|
Expression exp = p.parse(value);
|
||||||
|
Variable x = Variable('成本');
|
||||||
|
ContextModel cm = ContextModel()
|
||||||
|
..bindVariable(
|
||||||
|
x, Number(controller.totalCost.value));
|
||||||
|
exp.evaluate(EvaluationType.REAL, cm);
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
return '公式错误';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChanged: (String formula) {
|
||||||
|
try {
|
||||||
|
controller.calculateTotal(newFormula: formula);
|
||||||
|
} catch (e) {}
|
||||||
|
},
|
||||||
|
onTapOutside: (String value) {
|
||||||
|
controller.formula.value = value;
|
||||||
|
controller.isFormulaEditing.value = false;
|
||||||
|
},
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: ScreenAdaper.width(20),
|
||||||
|
vertical: ScreenAdaper.height(10)),
|
||||||
|
textController: formulaController)
|
||||||
|
: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
controller.isFormulaEditing.value = true;
|
||||||
|
},
|
||||||
|
child: Text('(${controller.formula})',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.height(30),
|
||||||
|
color: AppTheme.grey)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// controller.isFormulaEditing.value =
|
||||||
|
// !controller.isFormulaEditing.value;
|
||||||
|
// },
|
||||||
|
// icon: Icon(Icons.edit, size: ScreenAdaper.height(30)),
|
||||||
|
// ),
|
||||||
|
Obx(
|
||||||
|
() => Text(
|
||||||
|
'¥${controller.totalPrice.value}',
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.height(30),
|
||||||
|
fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildTotalCostRow() {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: ScreenAdaper.width(20),
|
||||||
|
vertical: ScreenAdaper.height(10)),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(width: 1, color: Colors.grey[200]!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'成本',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.height(30), fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Obx(
|
||||||
|
() => Text(
|
||||||
|
'¥${controller.totalCost.value}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.height(30),
|
||||||
|
fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +246,7 @@ class SaleQuotationPage extends StatelessWidget {
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Container(
|
Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
width: quantityWidth,
|
width: ScreenAdaper.width(quantityWidth),
|
||||||
// constraints: BoxConstraints(minWidth: quantityWidth),
|
// constraints: BoxConstraints(minWidth: quantityWidth),
|
||||||
child: Text(
|
child: Text(
|
||||||
'数量',
|
'数量',
|
||||||
|
@ -109,8 +255,8 @@ class SaleQuotationPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
width: unitPriceWidth,
|
width: ScreenAdaper.width(unitPriceWidth),
|
||||||
// constraints: BoxConstraints(minWidth: unitPriceWidth),
|
// constraints: BoxConstraints(minWidth: ScreenAdaper.width(unitPriceWidth)),
|
||||||
child: Text(
|
child: Text(
|
||||||
'单价',
|
'单价',
|
||||||
style: headerTitleStyle,
|
style: headerTitleStyle,
|
||||||
|
@ -118,7 +264,7 @@ class SaleQuotationPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
width: amountWidth,
|
width: ScreenAdaper.width(amountWidth),
|
||||||
child: Text(
|
child: Text(
|
||||||
'总价',
|
'总价',
|
||||||
style: headerTitleStyle,
|
style: headerTitleStyle,
|
||||||
|
@ -128,259 +274,291 @@ class SaleQuotationPage extends StatelessWidget {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildBody(SaleQuotationModel group, int groupIndex) {
|
Widget buildBody(int groupIndex) {
|
||||||
final titleStyle = TextStyle(
|
|
||||||
fontSize: ScreenAdaper.height(30),
|
|
||||||
color: AppTheme.nearlyBlack,
|
|
||||||
fontWeight: FontWeight.w600);
|
|
||||||
final subTextStyle =
|
|
||||||
TextStyle(color: AppTheme.grey, fontSize: ScreenAdaper.height(30));
|
|
||||||
|
|
||||||
return SliverStickyHeader.builder(
|
return SliverStickyHeader.builder(
|
||||||
builder: (context, state) => Container(
|
builder: (context, state) => buildGroupHeader(groupIndex, state),
|
||||||
height: ScreenAdaper.height(80),
|
|
||||||
color: headerBgcolor.withOpacity(1.0 - state.scrollPercentage),
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
controller.groups[groupIndex].isExpanded.value =
|
|
||||||
!controller.groups[groupIndex].isExpanded.value;
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Obx(() => controller.groups[groupIndex].isExpanded.value
|
|
||||||
? IconButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
onPressed: () {
|
|
||||||
controller.groups[groupIndex].isExpanded.value =
|
|
||||||
false;
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.expand_more,
|
|
||||||
color: AppTheme.nearlyBlack,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: IconButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
onPressed: () {
|
|
||||||
controller.groups[groupIndex].isExpanded.value =
|
|
||||||
true;
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.chevron_right,
|
|
||||||
color: AppTheme.nearlyBlack,
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
Text(
|
|
||||||
group.name,
|
|
||||||
style: titleStyle,
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
ModalUtil.confirm(
|
|
||||||
title: '确定要删除此组吗?',
|
|
||||||
onConfirm: () {
|
|
||||||
controller.removeGroup(groupIndex);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.remove_circle_outline_outlined,
|
|
||||||
color: AppTheme.nearlyBlack,
|
|
||||||
)),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
controller.addItems(groupIndex);
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.add_circle_outline,
|
|
||||||
color: AppTheme.nearlyBlack,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
))),
|
|
||||||
sliver: Obx(() => !controller.groups[groupIndex].isExpanded.value
|
sliver: Obx(() => !controller.groups[groupIndex].isExpanded.value
|
||||||
? SliverList(
|
? SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(context, i) => SizedBox(),
|
(context, i) => const SizedBox(),
|
||||||
childCount: 0,
|
childCount: 0,
|
||||||
))
|
))
|
||||||
: SliverList(
|
: SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(context, int i) => Slidable(
|
(context, int i) => buildRow(groupIndex, i),
|
||||||
key: UniqueKey(),
|
|
||||||
endActionPane: ActionPane(
|
|
||||||
extentRatio: 0.2,
|
|
||||||
motion: const DrawerMotion(),
|
|
||||||
children: [
|
|
||||||
SlidableAction(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
onPressed: (_) {
|
|
||||||
controller.groups[groupIndex].items.removeAt(i);
|
|
||||||
},
|
|
||||||
backgroundColor: AppTheme.dangerColor,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
icon: Icons.delete,
|
|
||||||
label: '删除',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: ScreenAdaper.width(20),
|
|
||||||
top: ScreenAdaper.height(15),
|
|
||||||
bottom: ScreenAdaper.height(15),
|
|
||||||
),
|
|
||||||
constraints:
|
|
||||||
BoxConstraints(minHeight: ScreenAdaper.height(100)),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(width: 1, color: Colors.grey[200]!),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
group.items[i].name,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: ScreenAdaper.height(30)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (group.items[i].spec != null)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'规格、型号: ',
|
|
||||||
style: subTextStyle,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
group.items[i].spec ?? '',
|
|
||||||
style: subTextStyle,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (group.items[i].unit != null)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'单位: ',
|
|
||||||
style: subTextStyle,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
group.items[i].unit ?? '',
|
|
||||||
style: subTextStyle,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (group.items[i].remark != null)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'备注: ',
|
|
||||||
style: subTextStyle,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'${group.items[i].remark ?? ''}${group.items[i].remark ?? ''}${controller.products[i].remark ?? ''}',
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: subTextStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
VerticalDivider(),
|
|
||||||
buildEditCell(
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
width: quantityWidth,
|
|
||||||
child: Text(
|
|
||||||
'${controller.groups[groupIndex].items[i].quantity}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: ScreenAdaper.height(25)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
groupIndex: groupIndex,
|
|
||||||
rowIndex: i,
|
|
||||||
field: 'quantity',
|
|
||||||
inputWidth: quantityWidth,
|
|
||||||
value: controller
|
|
||||||
.groups[groupIndex].items[i].quantity),
|
|
||||||
buildEditCell(
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
width: unitPriceWidth,
|
|
||||||
child: Text(
|
|
||||||
'${controller.groups[groupIndex].items[i].cost}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: ScreenAdaper.height(25)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
groupIndex: groupIndex,
|
|
||||||
rowIndex: i,
|
|
||||||
field: 'cost',
|
|
||||||
inputWidth: unitPriceWidth,
|
|
||||||
value: controller.groups[groupIndex].items[i].cost),
|
|
||||||
buildEditCell(
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
width: amountWidth,
|
|
||||||
child: Text(
|
|
||||||
'${controller.groups[groupIndex].items[i].amount}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: ScreenAdaper.height(25)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
groupIndex: groupIndex,
|
|
||||||
rowIndex: i,
|
|
||||||
field: 'amount',
|
|
||||||
inputWidth: amountWidth,
|
|
||||||
value:
|
|
||||||
controller.groups[groupIndex].items[i].amount),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
childCount: !controller.groups[groupIndex].isExpanded.value
|
childCount: !controller.groups[groupIndex].isExpanded.value
|
||||||
? 0
|
? 0
|
||||||
: group.items.length,
|
: controller.groups[groupIndex].items.length,
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildEditCell(Widget content,
|
Widget buildGroupHeader(int groupIndex, SliverStickyHeaderState state) {
|
||||||
{required int groupIndex,
|
final titleStyle = TextStyle(
|
||||||
required int rowIndex,
|
fontSize: ScreenAdaper.height(30),
|
||||||
required String field,
|
color: AppTheme.nearlyBlack,
|
||||||
required double inputWidth,
|
fontWeight: FontWeight.w600);
|
||||||
required dynamic value}) {
|
return Container(
|
||||||
|
height: ScreenAdaper.height(80),
|
||||||
|
color: headerBgcolor.withOpacity(1.0 - state.scrollPercentage),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
controller.groups[groupIndex].isExpanded.value =
|
||||||
|
!controller.groups[groupIndex].isExpanded.value;
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Obx(() => controller.groups[groupIndex].isExpanded.value
|
||||||
|
? IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () {
|
||||||
|
controller.groups[groupIndex].isExpanded.value =
|
||||||
|
false;
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.expand_more,
|
||||||
|
color: AppTheme.nearlyBlack,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () {
|
||||||
|
controller.groups[groupIndex].isExpanded.value = true;
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.chevron_right,
|
||||||
|
color: AppTheme.nearlyBlack,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
Text(
|
||||||
|
controller.groups[groupIndex].name,
|
||||||
|
style: titleStyle,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
ModalUtil.confirm(
|
||||||
|
title: '确定要删除此组吗?',
|
||||||
|
onConfirm: () {
|
||||||
|
controller.removeGroup(groupIndex);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete_outline,
|
||||||
|
color: AppTheme.nearlyBlack,
|
||||||
|
)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
controller.addItems(groupIndex);
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: AppTheme.nearlyBlack,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildRow(int groupIndex, int rowIndex) {
|
||||||
|
final subTextStyle =
|
||||||
|
TextStyle(color: AppTheme.grey, fontSize: ScreenAdaper.height(25));
|
||||||
|
return Slidable(
|
||||||
|
key: UniqueKey(),
|
||||||
|
endActionPane: ActionPane(
|
||||||
|
extentRatio: 0.2,
|
||||||
|
motion: const DrawerMotion(),
|
||||||
|
children: [
|
||||||
|
SlidableAction(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: (_) {
|
||||||
|
controller.removeItem(groupIndex, rowIndex);
|
||||||
|
},
|
||||||
|
backgroundColor: AppTheme.dangerColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
icon: Icons.delete,
|
||||||
|
label: '删除',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: ScreenAdaper.width(20),
|
||||||
|
top: ScreenAdaper.height(15),
|
||||||
|
bottom: ScreenAdaper.height(15),
|
||||||
|
),
|
||||||
|
constraints: BoxConstraints(minHeight: ScreenAdaper.height(100)),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(width: 1, color: Colors.grey[200]!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
controller.groups[groupIndex].items[rowIndex].name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: ScreenAdaper.height(30)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (controller.groups[groupIndex].items[rowIndex].spec !=
|
||||||
|
null)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'规格、型号: ',
|
||||||
|
style: subTextStyle,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
controller.groups[groupIndex].items[rowIndex].spec ??
|
||||||
|
'',
|
||||||
|
style: subTextStyle,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (controller.groups[groupIndex].items[rowIndex].unit !=
|
||||||
|
null)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'单位: ',
|
||||||
|
style: subTextStyle,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
controller.groups[groupIndex].items[rowIndex].unit ??
|
||||||
|
'',
|
||||||
|
style: subTextStyle,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (controller.groups[groupIndex].items[rowIndex].remark !=
|
||||||
|
null)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'备注: ',
|
||||||
|
style: subTextStyle,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'${controller.groups[groupIndex].items[rowIndex].remark ?? ''}${controller.groups[groupIndex].items[rowIndex].remark ?? ''}${controller.products[rowIndex].remark ?? ''}',
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: subTextStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VerticalDivider(),
|
||||||
|
buildEditCell<int>(
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
width: ScreenAdaper.width(quantityWidth),
|
||||||
|
child: Text(
|
||||||
|
'${controller.groups[groupIndex].items[rowIndex].quantity}',
|
||||||
|
style: TextStyle(fontSize: ScreenAdaper.height(25)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
groupIndex: groupIndex,
|
||||||
|
rowIndex: rowIndex,
|
||||||
|
field: 'quantity',
|
||||||
|
inputWidth: ScreenAdaper.width(quantityWidth),
|
||||||
|
value: controller.groups[groupIndex].items[rowIndex].quantity,
|
||||||
|
func: (value) {
|
||||||
|
// 取消失去焦点,并且弹窗警告
|
||||||
|
bool isValid = value > 0;
|
||||||
|
if (!isValid) {
|
||||||
|
SnackBarUtil().error('数量必须>0,若想删除产品0,请左滑');
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
}),
|
||||||
|
buildEditCell<int>(
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
width: ScreenAdaper.width(unitPriceWidth),
|
||||||
|
child: Text(
|
||||||
|
'${controller.groups[groupIndex].items[rowIndex].cost}',
|
||||||
|
style: TextStyle(fontSize: ScreenAdaper.height(25)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
groupIndex: groupIndex,
|
||||||
|
rowIndex: rowIndex,
|
||||||
|
field: 'cost',
|
||||||
|
inputWidth: ScreenAdaper.width(unitPriceWidth),
|
||||||
|
value: controller.groups[groupIndex].items[rowIndex].cost),
|
||||||
|
buildEditCell<int>(
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
width: ScreenAdaper.width(amountWidth),
|
||||||
|
child: Text(
|
||||||
|
'${controller.groups[groupIndex].items[rowIndex].amount}',
|
||||||
|
style: TextStyle(fontSize: ScreenAdaper.height(25)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
groupIndex: groupIndex,
|
||||||
|
rowIndex: rowIndex,
|
||||||
|
field: 'amount',
|
||||||
|
inputWidth: ScreenAdaper.width(amountWidth),
|
||||||
|
value: controller.groups[groupIndex].items[rowIndex].amount),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildEditCell<T>(
|
||||||
|
Widget content, {
|
||||||
|
required int groupIndex,
|
||||||
|
required int rowIndex,
|
||||||
|
required String field,
|
||||||
|
required double inputWidth,
|
||||||
|
required dynamic value,
|
||||||
|
Function(dynamic)? func,
|
||||||
|
}) {
|
||||||
return Obx(() => controller.editingcell[0] == groupIndex &&
|
return Obx(() => controller.editingcell[0] == groupIndex &&
|
||||||
controller.editingcell[1] == rowIndex &&
|
controller.editingcell[1] == rowIndex &&
|
||||||
controller.editingcell[2] == field
|
controller.editingcell[2] == field
|
||||||
? Container(
|
? SizedBox(
|
||||||
width: inputWidth,
|
width: inputWidth,
|
||||||
child: SkNumberInput(
|
child: SkNumberInput<T>(
|
||||||
hint: '',
|
hint: '',
|
||||||
isDense: true,
|
isDense: true,
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
|
validator: func,
|
||||||
|
onFieldSubmitted: (value) {
|
||||||
|
final editingData =
|
||||||
|
controller.groups[groupIndex].items[rowIndex].toJson();
|
||||||
|
editingData[field] = value as T;
|
||||||
|
controller.saveChanges(groupIndex, rowIndex,
|
||||||
|
SaleQuotationItemModel.fromJson(editingData), field);
|
||||||
|
},
|
||||||
|
onTapOutside: (dynamic value) {
|
||||||
|
final editingData =
|
||||||
|
controller.groups[groupIndex].items[rowIndex].toJson();
|
||||||
|
editingData[field] = value as T;
|
||||||
|
controller.saveChanges(groupIndex, rowIndex,
|
||||||
|
SaleQuotationItemModel.fromJson(editingData), field);
|
||||||
|
},
|
||||||
contentPadding: EdgeInsets.symmetric(
|
contentPadding: EdgeInsets.symmetric(
|
||||||
horizontal: ScreenAdaper.width(20),
|
horizontal: ScreenAdaper.width(20),
|
||||||
vertical: ScreenAdaper.height(10)),
|
vertical: ScreenAdaper.height(10)),
|
||||||
textController: TextEditingController(text: '${value}')),
|
textController:
|
||||||
|
TextEditingController(text: '${value == 0 ? '' : value}')),
|
||||||
)
|
)
|
||||||
: InkWell(
|
: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
|
import 'package:sk_base_mobile/constants/bg_color.dart';
|
||||||
|
import 'package:sk_base_mobile/models/base_search_more.model.dart';
|
||||||
|
import 'package:sk_base_mobile/models/base_search_more_controller.dart';
|
||||||
|
import 'package:sk_base_mobile/util/debouncer.dart';
|
||||||
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/empty.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/gradient_button.dart';
|
||||||
|
|
||||||
|
class SkMutilSearchMore<T extends BaseSearchMoreModel> extends StatelessWidget {
|
||||||
|
final Function(List<int>)? onOk;
|
||||||
|
final Function(T)? beforeSelectedCheck;
|
||||||
|
// ignore: prefer_typing_uninitialized_variables
|
||||||
|
final BaseSearchMoreController controller;
|
||||||
|
final bool enablePullUp;
|
||||||
|
final bool enablePullDown;
|
||||||
|
final bool isDialog;
|
||||||
|
|
||||||
|
final Function(int)? leadingBuilder;
|
||||||
|
SkMutilSearchMore(
|
||||||
|
{super.key,
|
||||||
|
required this.controller,
|
||||||
|
this.onOk,
|
||||||
|
this.isDialog = true,
|
||||||
|
this.beforeSelectedCheck,
|
||||||
|
this.leadingBuilder,
|
||||||
|
this.enablePullDown = true,
|
||||||
|
this.enablePullUp = true});
|
||||||
|
final listTitleTextStyle =
|
||||||
|
TextStyle(fontSize: ScreenAdaper.height(20), fontWeight: FontWeight.w600);
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: ScreenAdaper.width(20),
|
||||||
|
vertical: ScreenAdaper.height(20)),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
buildDialogHeader(),
|
||||||
|
SizedBox(
|
||||||
|
height: ScreenAdaper.height(defaultPadding) / 2,
|
||||||
|
),
|
||||||
|
buildSearchBar(),
|
||||||
|
SizedBox(
|
||||||
|
height: ScreenAdaper.height(defaultPadding) / 2,
|
||||||
|
),
|
||||||
|
Expanded(child: buildList()),
|
||||||
|
SizedBox(
|
||||||
|
height: ScreenAdaper.height(defaultPadding) / 2,
|
||||||
|
),
|
||||||
|
GradientButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (onOk != null) {
|
||||||
|
onOk!(controller.selectedIndex);
|
||||||
|
}
|
||||||
|
if (isDialog) {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttonText: '确定',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildDialogHeader() {
|
||||||
|
return Container(
|
||||||
|
height: ScreenAdaper.height(60),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'请选择',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.height(30),
|
||||||
|
fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: ScreenAdaper.height(40),
|
||||||
|
)))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildSearchBar() {
|
||||||
|
final doSearch = debouncer((String value) {
|
||||||
|
controller.searchKey.value = value;
|
||||||
|
controller.onRefresh();
|
||||||
|
}, delayTime: 500);
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: TextField(
|
||||||
|
controller: controller.searchBarTextConroller,
|
||||||
|
onChanged: (value) => doSearch(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
vertical: ScreenAdaper.height(10),
|
||||||
|
horizontal: ScreenAdaper.width(10)),
|
||||||
|
hintText: '名称',
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
|
prefixIcon: const Icon(Icons.search),
|
||||||
|
// 当searchBarController有值时不显示
|
||||||
|
suffixIcon: Obx(() => controller.searchKey.value.isEmpty
|
||||||
|
? const SizedBox()
|
||||||
|
: IconButton(
|
||||||
|
icon: const Icon(Icons.clear),
|
||||||
|
onPressed: () {
|
||||||
|
controller.searchKey.value = '';
|
||||||
|
controller.searchBarTextConroller.clear();
|
||||||
|
doSearch('');
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildList() {
|
||||||
|
return Obx(() => SmartRefresher(
|
||||||
|
enablePullDown: enablePullDown,
|
||||||
|
enablePullUp: enablePullUp,
|
||||||
|
controller: controller.refreshController,
|
||||||
|
onLoading: controller.onLoading,
|
||||||
|
onRefresh: controller.onRefresh,
|
||||||
|
child: controller.refreshController.isLoading
|
||||||
|
? const SizedBox()
|
||||||
|
: controller.list.isEmpty
|
||||||
|
? const Center(
|
||||||
|
child: Empty(text: '暂无数据'),
|
||||||
|
)
|
||||||
|
: ListView.separated(
|
||||||
|
separatorBuilder: (context, index) => const Divider(
|
||||||
|
color: AppTheme.dividerColor,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
itemCount: controller.list.length,
|
||||||
|
itemBuilder: (_, index) => buildItem(index))));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildItem(int index) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
controller.selectedIndex.contains(index)
|
||||||
|
? controller.selectedIndex.remove(index)
|
||||||
|
: controller.selectedIndex.add(index);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: ScreenAdaper.width(5),
|
||||||
|
vertical: ScreenAdaper.height(20)),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: leadingBuilder != null
|
||||||
|
? leadingBuilder!(index)
|
||||||
|
: Text(
|
||||||
|
'-',
|
||||||
|
style: TextStyle(fontSize: ScreenAdaper.height(30)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Obx(() => controller.selectedIndex.contains(index)
|
||||||
|
? Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
color: Colors.green,
|
||||||
|
size: ScreenAdaper.height(40),
|
||||||
|
)
|
||||||
|
: const SizedBox())
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,36 +2,42 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:sk_base_mobile/app_theme.dart';
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
|
import 'package:sk_base_mobile/util/snack_bar.util.dart';
|
||||||
|
|
||||||
class SkNumberInput extends StatefulWidget {
|
class SkNumberInput<T> extends StatefulWidget {
|
||||||
final TextEditingController textController;
|
final TextEditingController textController;
|
||||||
final Function(FocusNode)? onTap;
|
final Function(FocusNode)? onTap;
|
||||||
final Function(String)? onChanged;
|
final Function(String)? onChanged;
|
||||||
|
final Function(dynamic)? onTapOutside;
|
||||||
|
final Function(dynamic)? validator;
|
||||||
|
|
||||||
final bool isDense;
|
final bool isDense;
|
||||||
final EdgeInsetsGeometry? contentPadding;
|
final EdgeInsetsGeometry? contentPadding;
|
||||||
final bool autoFocus;
|
final bool autoFocus;
|
||||||
final bool isRequired;
|
final bool isRequired;
|
||||||
final String labelText;
|
final String labelText;
|
||||||
final String? hint;
|
final String? hint;
|
||||||
|
ValueChanged<int>? onFieldSubmitted;
|
||||||
SkNumberInput({
|
SkNumberInput(
|
||||||
super.key,
|
{super.key,
|
||||||
required this.textController,
|
required this.textController,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.hint,
|
this.hint,
|
||||||
this.isRequired = false,
|
this.onTapOutside,
|
||||||
this.labelText = '',
|
this.isRequired = false,
|
||||||
this.autoFocus = false,
|
this.labelText = '',
|
||||||
this.contentPadding,
|
this.onFieldSubmitted,
|
||||||
this.isDense = false,
|
this.autoFocus = false,
|
||||||
});
|
this.contentPadding,
|
||||||
|
this.isDense = false,
|
||||||
|
this.validator});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SkNumberInput> createState() => _SkNumberInputState();
|
State<SkNumberInput> createState() => _SkNumberInputState<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SkNumberInputState extends State<SkNumberInput> {
|
class _SkNumberInputState<T> extends State<SkNumberInput> {
|
||||||
late FocusNode focusNode = FocusNode();
|
late FocusNode focusNode = FocusNode();
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -53,8 +59,38 @@ class _SkNumberInputState extends State<SkNumberInput> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
controller: widget.textController,
|
controller: widget.textController,
|
||||||
|
onFieldSubmitted: (event) {
|
||||||
|
if (widget.onTapOutside != null) {
|
||||||
|
dynamic value;
|
||||||
|
if (T == double) {
|
||||||
|
value = double.tryParse(widget.textController.text);
|
||||||
|
|
||||||
|
widget.onTapOutside!(value as T);
|
||||||
|
} else {
|
||||||
|
value = int.tryParse(widget.textController.text);
|
||||||
|
}
|
||||||
|
if (widget.validator != null && !widget.validator!(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
widget.onTapOutside!(value as T);
|
||||||
|
}
|
||||||
|
},
|
||||||
onTapOutside: (event) {
|
onTapOutside: (event) {
|
||||||
FocusScope.of(context).unfocus();
|
if (widget.onTapOutside != null) {
|
||||||
|
dynamic value;
|
||||||
|
if (T == double) {
|
||||||
|
value = double.tryParse(widget.textController.text);
|
||||||
|
|
||||||
|
widget.onTapOutside!(value as T);
|
||||||
|
} else {
|
||||||
|
value = int.tryParse(widget.textController.text);
|
||||||
|
}
|
||||||
|
if (widget.validator != null && !widget.validator!(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
widget.onTapOutside!(value as T);
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onChanged: widget.onChanged ?? (_) {},
|
onChanged: widget.onChanged ?? (_) {},
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:get/get.dart';
|
||||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
import 'package:sk_base_mobile/app_theme.dart';
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
import 'package:sk_base_mobile/constants/bg_color.dart';
|
import 'package:sk_base_mobile/constants/bg_color.dart';
|
||||||
import 'package:sk_base_mobile/models/base_search_more.dart';
|
import 'package:sk_base_mobile/models/base_search_more_controller.dart';
|
||||||
import 'package:sk_base_mobile/util/debouncer.dart';
|
import 'package:sk_base_mobile/util/debouncer.dart';
|
||||||
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
import 'package:sk_base_mobile/widgets/empty.dart';
|
import 'package:sk_base_mobile/widgets/empty.dart';
|
||||||
|
@ -12,7 +12,7 @@ class SkSingleSearchMore<T> extends StatelessWidget {
|
||||||
final Function(T)? onSelected;
|
final Function(T)? onSelected;
|
||||||
final Function(T)? beforeSelectedCheck;
|
final Function(T)? beforeSelectedCheck;
|
||||||
// ignore: prefer_typing_uninitialized_variables
|
// ignore: prefer_typing_uninitialized_variables
|
||||||
final BaseSearchMore controller;
|
final BaseSearchMoreController controller;
|
||||||
final bool enablePullUp;
|
final bool enablePullUp;
|
||||||
final bool enablePullDown;
|
final bool enablePullDown;
|
||||||
final bool isDialog;
|
final bool isDialog;
|
||||||
|
|
|
@ -10,20 +10,27 @@ class SkTextInput extends StatefulWidget {
|
||||||
final String? hint;
|
final String? hint;
|
||||||
final bool isTextArea;
|
final bool isTextArea;
|
||||||
final bool isDense;
|
final bool isDense;
|
||||||
|
final Function(String)? onTapOutside;
|
||||||
|
final Function(String)? onChanged;
|
||||||
|
final String? Function(String?)? validator;
|
||||||
final EdgeInsetsGeometry? contentPadding;
|
final EdgeInsetsGeometry? contentPadding;
|
||||||
final bool autoFocus;
|
final bool autoFocus;
|
||||||
SkTextInput({
|
ValueChanged<String>? onFieldSubmitted;
|
||||||
super.key,
|
SkTextInput(
|
||||||
required this.textController,
|
{super.key,
|
||||||
this.onTap,
|
required this.textController,
|
||||||
this.hint,
|
this.onTap,
|
||||||
this.isRequired = false,
|
this.hint,
|
||||||
this.labelText = '',
|
this.onFieldSubmitted,
|
||||||
this.isTextArea = false,
|
this.isRequired = false,
|
||||||
this.autoFocus = false,
|
this.onTapOutside,
|
||||||
this.contentPadding,
|
this.labelText = '',
|
||||||
this.isDense = false,
|
this.onChanged,
|
||||||
});
|
this.isTextArea = false,
|
||||||
|
this.autoFocus = false,
|
||||||
|
this.contentPadding,
|
||||||
|
this.isDense = false,
|
||||||
|
this.validator});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SkTextInput> createState() => _SkTextInputState();
|
State<SkTextInput> createState() => _SkTextInputState();
|
||||||
|
@ -31,6 +38,7 @@ class SkTextInput extends StatefulWidget {
|
||||||
|
|
||||||
class _SkTextInputState extends State<SkTextInput> {
|
class _SkTextInputState extends State<SkTextInput> {
|
||||||
late FocusNode focusNode = FocusNode();
|
late FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -46,16 +54,28 @@ class _SkTextInputState extends State<SkTextInput> {
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
controller: widget.textController,
|
controller: widget.textController,
|
||||||
|
onChanged: (String value) {
|
||||||
|
if (widget.onChanged != null) {
|
||||||
|
widget.onChanged!(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
onTapOutside: (event) {
|
onTapOutside: (event) {
|
||||||
FocusScope.of(context).unfocus();
|
if (widget.onTapOutside != null) {
|
||||||
|
widget.onTapOutside!(widget.textController.text);
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
maxLines: widget.isTextArea ? 2 : 1, // 添加这行代码
|
maxLines: widget.isTextArea ? 2 : 1, // 添加这行代码
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (widget.onTap == null) {
|
if (widget.onTap != null) {
|
||||||
widget.onTap!(focusNode);
|
widget.onTap!(focusNode);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onFieldSubmitted: widget.onFieldSubmitted,
|
||||||
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
|
validator: widget.validator,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
errorStyle: const TextStyle(fontSize: 0, height: 0.01),
|
||||||
contentPadding: widget.contentPadding,
|
contentPadding: widget.contentPadding,
|
||||||
isDense: widget.isDense,
|
isDense: widget.isDense,
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
|
@ -75,8 +95,7 @@ class _SkTextInputState extends State<SkTextInput> {
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderSide:
|
borderSide: BorderSide(color: AppTheme.primaryColorLight, width: 2),
|
||||||
const BorderSide(color: AppTheme.primaryColorLight, width: 2),
|
|
||||||
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
|
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
|
||||||
hintText: widget.hint ?? '请输入',
|
hintText: widget.hint ?? '请输入',
|
||||||
),
|
),
|
||||||
|
|
|
@ -8,10 +8,12 @@ class GradientButton extends StatelessWidget {
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final String buttonText;
|
final String buttonText;
|
||||||
|
final Icon? icon;
|
||||||
const GradientButton(
|
const GradientButton(
|
||||||
{super.key,
|
{super.key,
|
||||||
this.buttonText = TextEnum.createInventoryInOutBtnText,
|
this.buttonText = TextEnum.createInventoryInOutBtnText,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
|
this.icon,
|
||||||
this.isLoading = false});
|
this.isLoading = false});
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -33,19 +35,30 @@ class GradientButton extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: onPressed ?? () => {},
|
onPressed: onPressed ?? () => {},
|
||||||
child: isLoading
|
child: Row(
|
||||||
? LoadingAnimationWidget.fourRotatingDots(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
color: AppTheme.nearlyWhite,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
size: ScreenAdaper.height(40),
|
children: [
|
||||||
)
|
if (icon != null) ...[
|
||||||
: Text(
|
icon!,
|
||||||
buttonText,
|
SizedBox(
|
||||||
style: TextStyle(
|
width: ScreenAdaper.width(10),
|
||||||
color: AppTheme.nearlyWhite,
|
)
|
||||||
fontWeight: FontWeight.bold,
|
],
|
||||||
fontSize: ScreenAdaper.height(25),
|
isLoading
|
||||||
),
|
? LoadingAnimationWidget.fourRotatingDots(
|
||||||
),
|
color: AppTheme.nearlyWhite,
|
||||||
|
size: ScreenAdaper.height(40),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
buttonText,
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.nearlyWhite,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: ScreenAdaper.height(25),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,24 @@ import 'package:sk_base_mobile/util/util.dart';
|
||||||
class SkAppbar extends StatelessWidget implements PreferredSizeWidget {
|
class SkAppbar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final List<Widget>? action;
|
final List<Widget>? action;
|
||||||
const SkAppbar({super.key, required this.title, this.action});
|
final bool hideLeading;
|
||||||
|
const SkAppbar(
|
||||||
|
{super.key, required this.title, this.action, this.hideLeading = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppBar(
|
return AppBar(
|
||||||
|
leading: hideLeading
|
||||||
|
? const SizedBox()
|
||||||
|
: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.arrow_back_ios,
|
||||||
|
size: ScreenAdaper.height(40),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyle(fontSize: ScreenAdaper.height(30)),
|
style: TextStyle(fontSize: ScreenAdaper.height(30)),
|
||||||
|
|
|
@ -576,6 +576,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.5.0"
|
||||||
|
math_expressions:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: math_expressions
|
||||||
|
sha256: "3576593617c3870d75728a751f6ec6e606706d44e363f088ac394b5a28a98064"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -65,6 +65,7 @@ dependencies:
|
||||||
flutter_sticky_header: ^0.6.5
|
flutter_sticky_header: ^0.6.5
|
||||||
flutter_slidable: ^3.1.0
|
flutter_slidable: ^3.1.0
|
||||||
pinyin: ^3.2.0
|
pinyin: ^3.2.0
|
||||||
|
math_expressions: ^2.4.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
Loading…
Reference in New Issue