feat: 电解控实时计算功能完成

This commit is contained in:
louis 2024-04-03 13:45:34 +08:00
parent 49ddf62751
commit 10145b3af7
17 changed files with 1022 additions and 390 deletions

View File

@ -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 = "山矿通";

View File

@ -0,0 +1,6 @@
import 'package:get/get_rx/src/rx_types/rx_types.dart';
class BaseSearchMoreModel {
RxBool isExpanded = true.obs;
String? name;
}

View File

@ -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 {

View File

@ -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,
};
} }

View File

@ -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,

View File

@ -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: '保存为模板',
)
],
);
}
}

View File

@ -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;

View File

@ -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 = [

View File

@ -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: () {

View File

@ -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())
],
),
),
);
}
}

View File

@ -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,

View File

@ -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;

View File

@ -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 ?? '请输入',
), ),

View File

@ -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),
),
)
]),
), ),
); );
} }

View File

@ -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)),

View File

@ -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:

View File

@ -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