feat: develop create inventory inout history dialog and develop core common form field.

This commit is contained in:
louis 2024-03-20 18:31:37 +08:00
parent 09385a4d77
commit f725a72a9d
18 changed files with 703 additions and 319 deletions

View File

@ -21,12 +21,18 @@ Future<Response> getUserInfo() {
); );
} }
// //
Future<Response<PaginationData>> getProjects(Map params) { Future<Response<PaginationData>> getProjects(Map params) {
return DioService.dio.get<PaginationData>(Urls.getProjects, return DioService.dio.get<PaginationData>(Urls.getProjects,
queryParameters: {'page': 1, 'pageSize': 10, ...params}); queryParameters: {'page': 1, 'pageSize': 10, ...params});
} }
//
Future<Response<PaginationData>> getProducts(Map params) {
return DioService.dio.get<PaginationData>(Urls.getProducts,
queryParameters: {'page': 1, 'pageSize': 10, ...params});
}
Future<Response> logout() { Future<Response> logout() {
return DioService.dio.post( return DioService.dio.post(
Urls.logout, Urls.logout,

View File

@ -23,11 +23,50 @@ class AppTheme {
static const String fontName = 'NotoSans'; static const String fontName = 'NotoSans';
static const Color activeNavigationBarColor = primaryColor; static const Color activeNavigationBarColor = primaryColor;
static const Color notActiveNavigationBarColor = Colors.grey; static const Color notActiveNavigationBarColor = Colors.grey;
static const Color dividerColor = Color.fromARGB(255, 197, 197, 197); static const Color dividerColor = Color.fromARGB(255, 224, 224, 224);
} }
final theme = ThemeData( final theme = ThemeData(
primarySwatch: MaterialColor(AppTheme.primaryColor.value, const {
50: AppTheme.primaryColorLight,
100: AppTheme.primaryColorLight,
200: AppTheme.primaryColorLight,
300: AppTheme.primaryColorLight,
400: AppTheme.primaryColorLight,
500: AppTheme.primaryColor,
600: AppTheme.primaryColorDark,
700: AppTheme.primaryColorDark,
800: AppTheme.primaryColorDark,
900: AppTheme.primaryColorDark,
}),
fontFamily: AppTheme.fontName, fontFamily: AppTheme.fontName,
datePickerTheme: DatePickerThemeData(
confirmButtonStyle: ButtonStyle(
textStyle: MaterialStateProperty.resolveWith<TextStyle?>(
(Set<MaterialState> states) {
return TextStyle(color: AppTheme.primaryColor);
},
),
),
todayBorder: const BorderSide(color: AppTheme.primaryColor),
todayBackgroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return AppTheme.primaryColor;
}
return null;
},
),
rangeSelectionBackgroundColor: AppTheme.primaryColor,
dayBackgroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return AppTheme.primaryColor;
}
return null;
},
),
),
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
primaryColor: AppTheme.primaryColor, primaryColor: AppTheme.primaryColor,
primaryColorDark: AppTheme.primaryColorDark, primaryColorDark: AppTheme.primaryColorDark,
@ -41,7 +80,7 @@ final theme = ThemeData(
unselectedLabelStyle: TextStyle(fontSize: ScreenAdaper.sp(20)), unselectedLabelStyle: TextStyle(fontSize: ScreenAdaper.sp(20)),
selectedLabelStyle: TextStyle(fontSize: ScreenAdaper.sp(20)), selectedLabelStyle: TextStyle(fontSize: ScreenAdaper.sp(20)),
selectedItemColor: AppTheme.primaryColor), selectedItemColor: AppTheme.primaryColor),
tabBarTheme: TabBarTheme( tabBarTheme: const TabBarTheme(
indicator: BoxDecoration( indicator: BoxDecoration(
border: border:
Border(bottom: BorderSide(width: 3, color: AppTheme.primaryColorDark)), Border(bottom: BorderSide(width: 3, color: AppTheme.primaryColorDark)),
@ -67,7 +106,7 @@ final theme = ThemeData(
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))), borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)), borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)),
borderSide: BorderSide( borderSide: const BorderSide(
color: AppTheme.primaryColorLight, width: 2), // color: AppTheme.primaryColorLight, width: 2), //
), ),
), ),

View File

@ -7,5 +7,6 @@ class Urls {
static String saveUserInfo = 'user/saveUserInfo'; static String saveUserInfo = 'user/saveUserInfo';
static String getUserInfo = 'account/profile'; static String getUserInfo = 'account/profile';
static String getProjects = 'project'; static String getProjects = 'project';
static String getProducts = 'product';
static String updateAvatar = 'user/updateAvatar'; static String updateAvatar = 'user/updateAvatar';
} }

View File

@ -0,0 +1,42 @@
import 'file.model.dart';
class CompanyModel {
CompanyModel({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.name,
required this.isDelete,
required this.files,
});
final int? id;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? name;
final int? isDelete;
final List<FileModel> files;
factory CompanyModel.fromJson(Map<String, dynamic> json) {
return CompanyModel(
id: json["id"],
createdAt: DateTime.tryParse(json["createdAt"] ?? ""),
updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""),
name: json["name"],
isDelete: json["isDelete"],
files: json["files"] == null
? []
: List<FileModel>.from(
json["files"]!.map((x) => FileModel.fromJson(x))),
);
}
Map<String, dynamic> toJson() => {
"id": id,
"createdAt": createdAt?.toIso8601String(),
"updatedAt": updatedAt?.toIso8601String(),
"name": name,
"isDelete": isDelete,
"files": files.map((x) => x?.toJson()).toList(),
};
}

View File

@ -0,0 +1,53 @@
class DictItemModel {
DictItemModel({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.creator,
required this.updater,
required this.label,
required this.value,
required this.orderNo,
required this.status,
required this.remark,
});
final int? id;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? creator;
final String? updater;
final String? label;
final String? value;
final int? orderNo;
final int? status;
final String? remark;
factory DictItemModel.fromJson(Map<String, dynamic> json) {
return DictItemModel(
id: json["id"],
createdAt: DateTime.tryParse(json["createdAt"] ?? ""),
updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""),
creator: json["creator"],
updater: json["updater"],
label: json["label"],
value: json["value"],
orderNo: json["orderNo"],
status: json["status"],
remark: json["remark"],
);
}
Map<String, dynamic> toJson() => {
"id": id,
"createdAt": createdAt?.toIso8601String(),
"updatedAt": updatedAt?.toIso8601String(),
"creator": creator,
"updater": updater,
"label": label,
"value": value,
"orderNo": orderNo,
"status": status,
"remark": remark,
};
}

View File

@ -0,0 +1,53 @@
class FileModel {
FileModel({
required this.id,
required this.name,
required this.extName,
required this.path,
required this.type,
required this.size,
required this.createdAt,
required this.username,
required this.bussinessRecordId,
required this.bussinessModule,
});
final int? id;
final String? name;
final String? extName;
final String? path;
final String? type;
final String? size;
final DateTime? createdAt;
final String? username;
final int? bussinessRecordId;
final String? bussinessModule;
factory FileModel.fromJson(Map<String, dynamic> json) {
return FileModel(
id: json["id"],
name: json["name"],
extName: json["extName"],
path: json["path"],
type: json["type"],
size: json["size"],
createdAt: DateTime.tryParse(json["createdAt"] ?? ""),
username: json["username"],
bussinessRecordId: json["bussinessRecordId"],
bussinessModule: json["bussinessModule"],
);
}
Map<String, dynamic> toJson() => {
"id": id,
"name": name,
"extName": extName,
"path": path,
"type": type,
"size": size,
"createdAt": createdAt?.toIso8601String(),
"username": username,
"bussinessRecordId": bussinessRecordId,
"bussinessModule": bussinessModule,
};
}

6
lib/models/index.dart Normal file
View File

@ -0,0 +1,6 @@
library models;
export './project.model.dart';
export './product.model.dart';
export './dict_item.model.dart';
export './company.model.dart';

View File

@ -0,0 +1,49 @@
class InventoryInOutCreateModel {
InventoryInOutCreateModel({
required this.inOrOut,
required this.productId,
required this.projectId,
required this.time,
required this.quantity,
required this.unitPrice,
required this.amount,
required this.agent,
this.remark,
});
final int? inOrOut;
final int? productId;
final int? projectId;
final DateTime? time;
final int? quantity;
final int? unitPrice;
final int? amount;
final String? agent;
final String? remark;
factory InventoryInOutCreateModel.fromJson(Map<String, dynamic> json) {
return InventoryInOutCreateModel(
inOrOut: json["inOrOut"],
productId: json["productId"],
projectId: json["projectId"],
time: DateTime.tryParse(json["time"] ?? ""),
quantity: json["quantity"],
unitPrice: json["unitPrice"],
amount: json["amount"],
agent: json["agent"],
remark: json["remark"],
);
}
Map<String, dynamic> toJson() => {
"inOrOut": inOrOut,
"productId": productId,
"projectId": projectId,
"time": time?.toIso8601String(),
"quantity": quantity,
"unitPrice": unitPrice,
"amount": amount,
"agent": agent,
"remark": remark,
};
}

View File

@ -1,129 +1,71 @@
import 'package:sk_base_mobile/models/dict_item.model.dart';
import 'package:sk_base_mobile/models/file.model.dart';
import 'company.model.dart';
class ProductModel { class ProductModel {
int? id; ProductModel({
String? createdAt; required this.id,
String? updatedAt; required this.createdAt,
String? name; required this.updatedAt,
String? remark; required this.name,
int? isDelete; required this.remark,
int? companyId; required this.isDelete,
int? unitId; required this.companyId,
String? namePinyin; required this.unitId,
List<Files>? files; required this.namePinyin,
Company? company; required this.files,
Unit? unit; required this.company,
required this.unit,
ProductModel(
{this.id,
this.createdAt,
this.updatedAt,
this.name,
this.remark,
this.isDelete,
this.companyId,
this.unitId,
this.namePinyin,
this.files,
this.company,
this.unit});
ProductModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
name = json['name'];
remark = json['remark'];
isDelete = json['isDelete'];
companyId = json['companyId'];
unitId = json['unitId'];
namePinyin = json['namePinyin'];
if (json['files'] != null) {
files = <Files>[];
json['files'].forEach((v) {
files!.add(new Files.fromJson(v));
}); });
}
company = final int? id;
json['company'] != null ? new Company.fromJson(json['company']) : null; final DateTime? createdAt;
unit = json['unit'] != null ? new Unit.fromJson(json['unit']) : null; final DateTime? updatedAt;
final String? name;
final String? remark;
final int? isDelete;
final int? companyId;
final int? unitId;
final String? namePinyin;
final List<FileModel> files;
final CompanyModel? company;
final DictItemModel? unit;
factory ProductModel.fromJson(Map<String, dynamic> json) {
return ProductModel(
id: json["id"],
createdAt: DateTime.tryParse(json["createdAt"] ?? ""),
updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""),
name: json["name"],
remark: json["remark"],
isDelete: json["isDelete"],
companyId: json["companyId"],
unitId: json["unitId"],
namePinyin: json["namePinyin"],
files: json["files"] == null
? []
: List<FileModel>.from(
json["files"]!.map((x) => FileModel.fromJson(x))),
company: json["company"] == null
? null
: CompanyModel.fromJson(json["company"]),
unit: json["unit"] == null ? null : DictItemModel.fromJson(json["unit"]),
);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() => {
final Map<String, dynamic> data = new Map<String, dynamic>(); "id": id,
data['id'] = this.id; "createdAt": createdAt?.toIso8601String(),
data['createdAt'] = this.createdAt; "updatedAt": updatedAt?.toIso8601String(),
data['updatedAt'] = this.updatedAt; "name": name,
data['name'] = this.name; "remark": remark,
data['remark'] = this.remark; "isDelete": isDelete,
data['isDelete'] = this.isDelete; "companyId": companyId,
data['companyId'] = this.companyId; "unitId": unitId,
data['unitId'] = this.unitId; "namePinyin": namePinyin,
data['namePinyin'] = this.namePinyin; "files": files.map((x) => x?.toJson()).toList(),
if (this.files != null) { "company": company?.toJson(),
data['files'] = this.files!.map((v) => v.toJson()).toList(); "unit": unit?.toJson(),
} };
if (this.company != null) {
data['company'] = this.company!.toJson();
}
if (this.unit != null) {
data['unit'] = this.unit!.toJson();
}
return data;
}
}
class Files {
int? id;
String? path;
Files({this.id, this.path});
Files.fromJson(Map<String, dynamic> json) {
id = json['id'];
path = json['path'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['path'] = this.path;
return data;
}
}
class Company {
int? id;
String? name;
Company({this.id, this.name});
Company.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
return data;
}
}
class Unit {
int? id;
String? label;
Unit({this.id, this.label});
Unit.fromJson(Map<String, dynamic> json) {
id = json['id'];
label = json['label'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['label'] = this.label;
return data;
}
} }

View File

@ -26,7 +26,7 @@ class DateTimeInput extends StatelessWidget {
height: defaultPadding / 2, height: defaultPadding / 2,
), ),
InkWell( InkWell(
onTap: () => controller.showDatePick(context), onTap: () => controller.showDatePick(),
child: Obx(() => DateTimeContainer( child: Obx(() => DateTimeContainer(
text: controller.selectedDate.isEmpty text: controller.selectedDate.isEmpty
? 'dd/mm/yyyy' ? 'dd/mm/yyyy'

View File

@ -3,10 +3,13 @@ import 'package:flutter_typeahead/flutter_typeahead.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/constants/constants.dart'; import 'package:sk_base_mobile/constants/constants.dart';
import 'package:sk_base_mobile/models/product.model.dart'; import 'package:sk_base_mobile/models/index.dart';
import 'package:sk_base_mobile/models/project.model.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/components/date_time.dart';
import 'package:sk_base_mobile/widgets/core/search_select.dart'; import 'package:sk_base_mobile/util/date.util.dart';
import 'package:sk_base_mobile/widgets/empty.dart'; import 'package:sk_base_mobile/widgets/core/zt_number_input.dart';
import 'package:sk_base_mobile/widgets/core/zt_search_select.dart';
import 'package:sk_base_mobile/widgets/core/zk_date_picker.dart';
import 'package:sk_base_mobile/widgets/core/zt_text_input.dart';
import 'package:sk_base_mobile/widgets/gradient_button.dart'; import 'package:sk_base_mobile/widgets/gradient_button.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart';
@ -35,19 +38,19 @@ class NewInventoryInout extends StatelessWidget {
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () { onTap: () {
// //
FocusScope.of(Get.context!).unfocus(); // FocusScope.of(Get.context!).unfocus();
}, },
child: Stack( child: Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
buildTitle(), buildTitle(),
Expanded( SingleChildScrollView(
child: SingleChildScrollView(
child: buildForm(), child: buildForm(),
), ),
), Spacer(),
Obx(() => GradientButton( Obx(() => GradientButton(
onPressed: () => {controller.insertTask(context)}, onPressed: () => {controller.insertTask(context)},
isLoading: controller.loading.value, isLoading: controller.loading.value,
@ -55,7 +58,8 @@ class NewInventoryInout extends StatelessWidget {
], ],
)) ))
], ],
))), ),
)),
); );
} }
@ -89,6 +93,8 @@ class NewInventoryInout extends StatelessWidget {
} }
Widget buildForm() { Widget buildForm() {
double formVerticalGap = 20.0;
double formHorizontalGap = 20.0;
final children = [ final children = [
Row(children: [ Row(children: [
Expanded( Expanded(
@ -96,13 +102,37 @@ class NewInventoryInout extends StatelessWidget {
child: buildProjectPicker(), child: buildProjectPicker(),
), ),
SizedBox( SizedBox(
width: ScreenAdaper.width(10), width: ScreenAdaper.width(formHorizontalGap),
), ),
Expanded( Expanded(
flex: 3, flex: 3,
child: buildProductPicker(), child: buildProductPicker(),
) ),
]), ]),
SizedBox(
height: ScreenAdaper.height(formVerticalGap),
),
Row(
children: [
Expanded(flex: 1, child: buildDatePicker()),
SizedBox(
width: ScreenAdaper.width(formHorizontalGap),
),
Expanded(flex: 1, child: buildQuantity()),
],
),
SizedBox(
height: ScreenAdaper.height(formVerticalGap),
),
Row(
children: [Expanded(child: buildAgent())],
),
SizedBox(
height: ScreenAdaper.height(formVerticalGap),
),
Row(
children: [Expanded(child: buildRemark())],
),
]; ];
final child = Column( final child = Column(
@ -122,147 +152,95 @@ class NewInventoryInout extends StatelessWidget {
)); ));
} }
///
Widget buildProjectPicker() { Widget buildProjectPicker() {
return SearchSelectComponent( return ZtSearchSelect(
isRequired: true,
textController: controller.projectTextController, textController: controller.projectTextController,
labelText: '项目', labelText: '项目',
itemTextBuilder: <ProjectModel>(ProjectModel itemData) => itemBuilder: (itemData) => Container(
Text('${itemData?.name}'), padding: EdgeInsets.symmetric(
); vertical: ScreenAdaper.height(15),
// return TypeAheadField<ProjectModel>( horizontal: ScreenAdaper.width(20)),
// hideOnUnfocus: false, child: Text(
// controller: controller.projectTextController, '${itemData?.name}',
// suggestionsCallback: (String keyword) { style: TextStyle(fontSize: ScreenAdaper.sp(20)),
// return controller.getProjects(keyword: keyword); )),
// }, suggestionsCallback: (String keyword) {
// builder: (context, _, focusNode) { return controller.getProjects(keyword: keyword);
// return TextFormField( },
// focusNode: focusNode, onClear: () {
// controller: _, controller.payload.remove('projectId');
// decoration: InputDecoration( },
// focusedBorder: OutlineInputBorder( onSelected: (ProjectModel project) {
// borderSide: const BorderSide( controller.projectTextController.text = project.name ?? '';
// color: AppTheme.primaryColorLight, width: 2), controller.payload['projectId'] = project.id;
// borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
// suffixIcon: _.text.isNotEmpty && focusNode.hasFocus
// ? IconButton(
// icon: const Icon(Icons.clear),
// // TextFormField的值
// onPressed: () => _.clear(),
// )
// : const SizedBox(),
// floatingLabelBehavior: FloatingLabelBehavior.always,
// label: const Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisSize: MainAxisSize.min,
// children: [
// Text(
// "*",
// style: TextStyle(color: Colors.red),
// ),
// Text(
// "项目",
// ),
// ])));
// },
// emptyBuilder: (_) => Container(
// alignment: Alignment.center,
// width: ScreenAdaper.width(200),
// height: ScreenAdaper.height(50),
// child: Text(
// '未找到项目',
// style: TextStyle(fontSize: ScreenAdaper.sp(20)),
// ),
// ),
// itemBuilder: (context, project) {
// return ListTile(
// title: Text(project.name ?? ''),
// );
// },
// onSelected: (ProjectModel project) {
// controller.projectTextController.text = project.name ?? '';
// controller.payload['project'] = project.id;
// });
// return Obx(() => DropdownButtonFormField<int>(
// isExpanded: true,
// isDense: false,
// style: TextStyle(
// fontSize: ScreenAdaper.sp(22), color: AppTheme.nearlyBlack),
// // value: _dropdownValue,
// decoration: InputDecoration(
// contentPadding: EdgeInsets.symmetric(
// vertical: ScreenAdaper.height(10),
// horizontal: ScreenAdaper.width(10)),
// floatingLabelBehavior: FloatingLabelBehavior.always,
// border: OutlineInputBorder(
// borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
// label: const Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisSize: MainAxisSize.min,
// children: [
// Text(
// "*",
// style: TextStyle(color: Colors.red),
// ),
// Text("项目"),
// ]),
// ),
// onChanged: (int? newValue) {
// // setState(() {
// // _dropdownValue = newValue;
// // });
// },
// // icon: Icon(
// // Icons.arrow_drop_down_outlined,
// // size: ScreenAdaper.sp(40),
// // ),
// items: controller.projects
// .map<DropdownMenuItem<int>>((ProjectModel model) {
// return DropdownMenuItem<int>(
// value: model.id,
// child: Text(
// model.name ?? '',
// style: TextStyle(
// fontSize: ScreenAdaper.sp(30),
// overflow: TextOverflow.ellipsis),
// ),
// );
// }).toList(),
// ));
}
Widget buildProductPicker() {
return TypeAheadField<ProductModel>(suggestionsCallback: (search) {
return [ProductModel(name: 'aaa'), ProductModel(name: 'bbb')];
}, builder: (context, controller, focusNode) {
return TextField(
controller: controller,
focusNode: focusNode,
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
label: const Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"*",
style: TextStyle(color: Colors.red),
),
Text("产品"),
])));
}, itemBuilder: (context, product) {
return ListTile(
title: Text(product.name ?? ''),
subtitle: Text(product.name ?? ''),
);
}, onSelected: (city) {
// Navigator.of(context).push<void>(
// MaterialPageRoute(
// builder: (context) => CityPage(city: city),
// ),
// );
}); });
} }
///
Widget buildProductPicker() {
return ZtSearchSelect(
isRequired: true,
textController: controller.productTextController,
labelText: '产品',
itemBuilder: (itemData) => ListTile(
title: Text('${itemData?.name}'),
subtitle: Text('${itemData?.company?.name ?? ''}'),
),
suggestionsCallback: (String keyword) {
return controller.getProducts(keyword: keyword);
},
onClear: () {
controller.payload.remove('productId');
},
onSelected: (ProductModel product) {
controller.productTextController.text = product.name ?? '';
controller.payload['productId'] = product.id;
});
}
///
Widget buildDatePicker() {
return ZtDatePicker(
textController: controller.dateTextController,
onClear: () {
controller.payload.remove('time');
},
onDateSelected: (date) {
if (date != null) {
controller.dateTextController.text = DateUtil.format(date);
controller.payload['time'] = controller.dateTextController.text;
}
},
);
}
///
Widget buildQuantity() {
return ZtNumberInput(
textController: controller.quantityTextController,
labelText: '数量',
isRequired: true,
);
}
///
Widget buildAgent() {
return ZtTextInput(
textController: controller.agentTextController,
labelText: '经办人',
isRequired: true,
);
}
///
Widget buildRemark() {
return ZtTextInput(
textController: controller.remarkTextController,
labelText: '备注',
isRequired: true,
isTextArea: true,
);
}
} }

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sk_base_mobile/db_helper/dbHelper.dart'; import 'package:sk_base_mobile/db_helper/dbHelper.dart';
import 'package:sk_base_mobile/models/base_response.dart'; import 'package:sk_base_mobile/models/base_response.dart';
import 'package:sk_base_mobile/models/product.model.dart';
import 'package:sk_base_mobile/models/project.model.dart'; import 'package:sk_base_mobile/models/project.model.dart';
import 'package:sk_base_mobile/models/task_model.dart'; import 'package:sk_base_mobile/models/task_model.dart';
import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart'; import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart';
@ -30,13 +31,14 @@ class NewInventoryInoutController extends GetxController {
final description = TextEditingController().obs; final description = TextEditingController().obs;
final category = TextEditingController().obs; final category = TextEditingController().obs;
RxList<ProjectModel> projects = <ProjectModel>[].obs; RxList<ProjectModel> projects = <ProjectModel>[].obs;
RxList<ProductModel> products = <ProductModel>[].obs;
final projectTextController = TextEditingController(); final projectTextController = TextEditingController();
final productTextController = TextEditingController();
final dateTextController = TextEditingController();
final quantityTextController = TextEditingController();
final agentTextController = TextEditingController();
final remarkTextController = TextEditingController();
Map<String, dynamic> payload = {}; Map<String, dynamic> payload = {};
@override
void onReady() {
super.onReady();
getProjects();
}
Future<List<ProjectModel>> getProjects({String? keyword}) async { Future<List<ProjectModel>> getProjects({String? keyword}) async {
final res = final res =
@ -48,6 +50,16 @@ class NewInventoryInoutController extends GetxController {
return projects; return projects;
} }
Future<List<ProductModel>> getProducts({String? keyword}) async {
final res =
await Api.getProducts({'page': 1, 'pageSize': 10, 'name': keyword});
if (res.data != null) {
products.assignAll(
res.data!.items.map((e) => ProductModel.fromJson(e)).toList());
}
return products;
}
picStartTime(BuildContext context) async { picStartTime(BuildContext context) async {
var picker = var picker =
await showTimePicker(context: context, initialTime: TimeOfDay.now()); await showTimePicker(context: context, initialTime: TimeOfDay.now());
@ -66,11 +78,11 @@ class NewInventoryInoutController extends GetxController {
} }
} }
showDatePick(BuildContext context) async { showDatePick() async {
var picker = await showDatePicker( final picker = await showDatePicker(
context: context, context: Get.context!,
initialDate: DateTime.now(), initialDate: DateTime.now(),
firstDate: DateTime.now(), firstDate: DateTime(2000, 1, 1),
lastDate: DateTime.now().add(const Duration(days: 7))); lastDate: DateTime.now().add(const Duration(days: 7)));
if (picker != null) { if (picker != null) {
pickedDate = picker; pickedDate = picker;
@ -125,7 +137,7 @@ class NewInventoryInoutController extends GetxController {
if (selectedDate.isEmpty) { if (selectedDate.isEmpty) {
// selectedDate.value = // selectedDate.value =
// '${Utils.addPrefix(DateTime.now().day.toString())}/${Utils.addPrefix(DateTime.now().month.toString())}/${Utils.addPrefix(DateTime.now().year.toString())}'; // '${Utils.addPrefix(DateTime.now().day.toString())}/${Utils.addPrefix(DateTime.now().month.toString())}/${Utils.addPrefix(DateTime.now().year.toString())}';
showDatePick(context); showDatePick();
} }
if (startTime.isEmpty) { if (startTime.isEmpty) {
picStartTime(context); picStartTime(context);

View File

@ -1,6 +1,11 @@
import 'package:date_format/date_format.dart'; import 'package:date_format/date_format.dart';
class DateUtil { class DateUtil {
/// YYYY-MM-DD
static String format(DateTime date, {List<String>? formats}) {
return formatDate(date, formats ?? ['yyyy', '-', 'mm', '-', 'dd']);
}
/// ///
static String getMonth(DateTime date) { static String getMonth(DateTime date) {
String formattedDate = '${date.month}'; String formattedDate = '${date.month}';

View File

@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:sk_base_mobile/util/date.util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class ZtDatePicker extends StatelessWidget {
final TextEditingController textController;
final VoidCallback? onClear;
final Function? onDateSelected;
const ZtDatePicker({
super.key,
this.onClear,
this.onDateSelected,
required this.textController,
});
@override
Widget build(BuildContext context) {
return TextFormField(
controller: textController,
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
prefixIcon: Icon(
Icons.date_range_outlined,
size: ScreenAdaper.sp(40),
),
suffixIcon: textController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
// TextFormField的值
onPressed: () {
textController.clear();
if (onClear != null) {
onClear!();
}
},
)
: const SizedBox(),
hintText: '请选择',
label: const Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"*",
style: TextStyle(color: Colors.red),
),
Text(
'日期',
),
])),
keyboardType: TextInputType.none,
onTap: () async {
final picker = await showDatePicker(
context: Get.overlayContext!,
initialDate: DateTime.now(),
helpText: '选择的日期',
confirmText: '确定',
cancelText: '取消',
firstDate: DateTime(2000, 1, 1),
lastDate: DateTime.now().add(const Duration(days: 7)));
if (onDateSelected != null) {
onDateSelected!(picker);
}
},
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/bg_color.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class ZtNumberInput extends StatelessWidget {
final TextEditingController textController;
final VoidCallback? onTap;
final bool isRequired;
final String labelText;
final String? hint;
ZtNumberInput(
{super.key,
required this.textController,
this.onTap,
this.hint,
this.isRequired = false,
this.labelText = ''});
@override
Widget build(BuildContext context) {
return TextFormField(
controller: textController,
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
onTap: onTap ?? () {},
keyboardType: TextInputType.number,
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
label: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (isRequired)
const Text(
"*",
style: TextStyle(color: Colors.red),
),
Text(
labelText,
),
]),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: AppTheme.primaryColorLight, width: 2),
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
hintText: hint ?? '请输入',
),
);
}
}

View File

@ -1,22 +1,25 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:flutter_typeahead/flutter_typeahead.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/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class SearchSelectComponent extends StatelessWidget { class ZtSearchSelect extends StatelessWidget {
final TextEditingController textController; final TextEditingController textController;
final Function? suggestionsCallback; final Function? suggestionsCallback;
final Function? onSelected; final Function? onSelected;
final VoidCallback? onClear;
final String labelText; final String labelText;
final bool isRequired; final bool isRequired;
final Widget Function<T>(T itemData)? itemTextBuilder; final Widget Function(dynamic itemData)? itemBuilder;
const SearchSelectComponent( const ZtSearchSelect(
{super.key, {super.key,
required this.textController, required this.textController,
required this.labelText, required this.labelText,
required this.itemTextBuilder, required this.itemBuilder,
this.suggestionsCallback, required this.suggestionsCallback,
this.onSelected, this.onSelected,
this.onClear,
this.isRequired = false}); this.isRequired = false});
@override @override
@ -29,10 +32,17 @@ class SearchSelectComponent extends StatelessWidget {
? suggestionsCallback!(keyword) ? suggestionsCallback!(keyword)
: []; : [];
}, },
debounceDuration: const Duration(milliseconds: 500),
itemSeparatorBuilder: (context, index) => const Divider(
color: AppTheme.dividerColor,
),
builder: (context, _, focusNode) { builder: (context, _, focusNode) {
return TextFormField( return TextFormField(
focusNode: focusNode, focusNode: focusNode,
controller: _, controller: _,
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
decoration: InputDecoration( decoration: InputDecoration(
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: const BorderSide( borderSide: const BorderSide(
@ -42,10 +52,16 @@ class SearchSelectComponent extends StatelessWidget {
? IconButton( ? IconButton(
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
// TextFormField的值 // TextFormField的值
onPressed: () => _.clear(), onPressed: () {
_.clear();
if (onClear != null) {
onClear!();
}
},
) )
: const SizedBox(), : const SizedBox(),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
hintText: '请选择',
label: Row( label: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -70,12 +86,14 @@ class SearchSelectComponent extends StatelessWidget {
), ),
), ),
itemBuilder: (context, itemData) { itemBuilder: (context, itemData) {
return ListTile( return itemBuilder!(itemData);
title: itemTextBuilder!(itemData),
);
}, },
onSelected: (selectedItemData) { onSelected: (selectedItemData) {
onSelected ?? onSelected!(selectedItemData); //
FocusScope.of(context).unfocus();
if (onSelected != null) {
onSelected!(selectedItemData);
}
}); });
} }
} }

View File

@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/bg_color.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class ZtTextInput extends StatelessWidget {
final TextEditingController textController;
final VoidCallback? onTap;
final bool isRequired;
final String labelText;
final String? hint;
final bool isTextArea;
const ZtTextInput(
{super.key,
required this.textController,
this.onTap,
this.hint,
this.isRequired = false,
this.labelText = '',
this.isTextArea = false});
@override
Widget build(BuildContext context) {
return TextFormField(
controller: textController,
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
maxLines: isTextArea ? null : 1, //
onTap: onTap ?? () {},
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
label: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (isRequired)
const Text(
"*",
style: TextStyle(color: Colors.red),
),
Text(
labelText,
),
]),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: AppTheme.primaryColorLight, width: 2),
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
hintText: hint ?? '请输入',
),
);
}
}

View File

@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class Empty extends StatelessWidget { class Empty extends StatelessWidget {
String? text; final String? text;
Empty({super.key, this.text}); const Empty({super.key, this.text});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -13,7 +13,7 @@ class Empty extends StatelessWidget {
Center( Center(
child: Image( child: Image(
height: ScreenAdaper.height(130), height: ScreenAdaper.height(130),
image: AssetImage('assets/images/empty_icon.png'))), image: const AssetImage('assets/images/empty_icon.png'))),
Text( Text(
text ?? '', text ?? '',
textAlign: TextAlign.center, textAlign: TextAlign.center,