feat: inventory inout history

This commit is contained in:
louis 2024-03-21 14:09:49 +08:00
parent f725a72a9d
commit b0c51290cf
30 changed files with 657 additions and 197 deletions

View File

@ -15,24 +15,9 @@ migration:
- platform: root
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: android
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: ios
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: linux
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: macos
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: web
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: windows
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
# User provided section

View File

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

View File

@ -39,7 +39,13 @@ final theme = ThemeData(
800: AppTheme.primaryColorDark,
900: AppTheme.primaryColorDark,
}),
primaryColorLight: AppTheme.primaryColorLight,
fontFamily: AppTheme.fontName,
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
foregroundColor:
MaterialStateProperty.all<Color>(AppTheme.primaryColor), //
)),
datePickerTheme: DatePickerThemeData(
confirmButtonStyle: ButtonStyle(
textStyle: MaterialStateProperty.resolveWith<TextStyle?>(
@ -57,7 +63,7 @@ final theme = ThemeData(
return null;
},
),
rangeSelectionBackgroundColor: AppTheme.primaryColor,
// rangeSelectionBackgroundColor: AppTheme.primaryColor,
dayBackgroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
@ -91,7 +97,7 @@ final theme = ThemeData(
backgroundColor: AppTheme.primaryColor,
titleTextStyle: TextStyle(
color: Colors.black,
fontSize: ScreenAdaper.sp(25),
fontSize: ScreenAdaper.sp(30),
fontWeight: FontWeight.bold),
),
inputDecorationTheme: InputDecorationTheme(

View File

@ -8,5 +8,6 @@ class Urls {
static String getUserInfo = 'account/profile';
static String getProjects = 'project';
static String getProducts = 'product';
static String getInventoryInout = 'materials-in-out';
static String updateAvatar = 'user/updateAvatar';
}

View File

@ -1,3 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
@ -5,6 +6,7 @@ import 'package:sk_base_mobile/constants/constants.dart';
import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart';
import 'package:sk_base_mobile/store/auth.store.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/core/zt_base_date_picker.dart';
class CustomAppBar extends StatelessWidget {
CustomAppBar({super.key});
@ -44,27 +46,69 @@ class CustomAppBar extends StatelessWidget {
const Spacer(
flex: 10,
),
Container(
height: ScreenAdaper.height(70),
width: ScreenAdaper.width(70),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: AppTheme.primaryColorLight,
boxShadow: [
BoxShadow(
color: AppTheme.primaryColor,
blurRadius: ScreenAdaper.sp(20),
offset: Offset(0, ScreenAdaper.height(10)))
]),
child: Icon(
Icons.account_circle_outlined,
size: ScreenAdaper.sp(40),
color: Colors.white,
),
),
buildDatePicker(),
if (ScreenAdaper.isLandspace()) const Spacer(),
],
),
);
}
Widget buildDatePicker() {
return InkWell(
onTap: () async {
showCupertinoModalPopup(
context: Get.overlayContext!,
builder: (BuildContext context) {
return ZtBaseDatePicker();
},
);
// showCupertinoModalPopup(
// context: Get.overlayContext!,
// builder: (BuildContext context) {
// return Container(
// height: 200,
// color: Colors.white,
// child: CupertinoDatePicker(
// mode: CupertinoDatePickerMode.date,
// initialDateTime: DateTime.now(),
// onDateTimeChanged: (DateTime newDateTime) {
// //
// },
// ),
// );
// },
// );
// final picker = await CupertinoDatePicker(
// context: Get.overlayContext!,
// initialDate: DateTime.now(),
// helpText: '选择的日期',
// confirmText: '确定',
// cancelText: '取消',
// firstDate: DateTime(2000, 1, 1),
// lastDate: DateTime.now().add(const Duration(days: 7)));
},
child: Container(
height: ScreenAdaper.height(70),
width: ScreenAdaper.width(70),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: AppTheme.primaryColorLight,
gradient: LinearGradient(colors: [
AppTheme.primaryColorLight,
AppTheme.primaryColor,
]),
boxShadow: [
BoxShadow(
color: AppTheme.primaryColor,
blurRadius: ScreenAdaper.sp(20),
offset: Offset(0, ScreenAdaper.height(10)))
]),
child: Icon(
Icons.calendar_month_outlined,
size: ScreenAdaper.sp(40),
color: Colors.white,
),
),
);
}
}

View File

@ -16,7 +16,7 @@ class DateContainer extends StatelessWidget {
duration: const Duration(milliseconds: 200),
height: ScreenAdaper.height(120),
width: ScreenAdaper.width(110),
margin: EdgeInsets.only(left: ScreenAdaper.width(size.width) * 0.05),
margin: EdgeInsets.only(right: ScreenAdaper.width(size.width) * 0.05),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(ScreenAdaper.sp(20)),

View File

@ -1,5 +1,3 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart';
@ -16,37 +14,37 @@ class Dates extends StatelessWidget {
children: [
Obx(
() => Text(
DateUtil.getMonth(DateTime.now().add(Duration(days: index))),
DateUtil.getMonth(DateTime.now().add(Duration(days: -index))),
style: TextStyle(
color: controller.currentIndex.value == index
? Colors.white
: Colors.black,
fontWeight: FontWeight.bold,
fontSize: ScreenAdaper.sp(16),
fontSize: ScreenAdaper.sp(18),
height: 0),
),
),
Obx(
() => Text(
DateUtil.getDate(DateTime.now().add(Duration(days: index))),
DateUtil.getDate(DateTime.now().add(Duration(days: -index))),
style: TextStyle(
color: controller.currentIndex.value == index
? Colors.white
: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 26,
fontSize: ScreenAdaper.sp(30),
height: 0),
),
),
Obx(
() => Text(
DateUtil.getDay(DateTime.now().add(Duration(days: index))),
DateUtil.getDay(DateTime.now().add(Duration(days: -index))),
style: TextStyle(
color: controller.currentIndex.value == index
? Colors.white
: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 13),
fontSize: ScreenAdaper.sp(16)),
),
)
],

View File

@ -56,6 +56,7 @@ class Grid extends StatelessWidget {
)
: GridView.builder(
padding: const EdgeInsets.only(top: 40),
reverse: true,
itemCount: controller.list[ind].length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAsis, childAspectRatio: ratio),

View File

@ -9,32 +9,36 @@ class TaskPageBody extends StatelessWidget {
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: Container(
margin: EdgeInsets.only(top: ScreenAdaper.height(25)),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(ScreenAdaper.sp(40)),
topLeft: Radius.circular(ScreenAdaper.sp(40))),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.white.withOpacity(.6),
Colors.white.withOpacity(.5),
Colors.white.withOpacity(.4),
Colors.white.withOpacity(.3),
Colors.white.withOpacity(.2),
Colors.white.withOpacity(.0),
Colors.white.withOpacity(.0),
Colors.white.withOpacity(.0),
Colors.white.withOpacity(.0),
Colors.white.withOpacity(.0),
])),
child: TaskPageView(),
)),
buildBackground(),
ChangeButtonRow(),
],
);
}
Widget buildBackground() {
return Positioned.fill(
child: Container(
margin: EdgeInsets.only(top: ScreenAdaper.height(25)),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(ScreenAdaper.sp(40)),
topLeft: Radius.circular(ScreenAdaper.sp(40))),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.white.withOpacity(.6),
Colors.white.withOpacity(.5),
Colors.white.withOpacity(.4),
Colors.white.withOpacity(.3),
Colors.white.withOpacity(.2),
Colors.white.withOpacity(.0),
Colors.white.withOpacity(.0),
Colors.white.withOpacity(.0),
Colors.white.withOpacity(.0),
Colors.white.withOpacity(.0),
])),
child: TaskPageView(),
));
}
}

View File

@ -21,8 +21,9 @@ class UperBody extends StatelessWidget {
height: ScreenAdaper.height(150),
child: ListView.builder(
controller: controller.scrollController,
itemCount: 7,
shrinkWrap: true,
reverse: true,
itemCount: 20,
scrollDirection: Axis.horizontal,
padding: EdgeInsets.only(
bottom: ScreenAdaper.height(20), top: defaultPadding),

View File

@ -3,11 +3,10 @@ import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/db_helper/dbHelper.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart';
import 'package:sk_base_mobile/util/date.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/apis/api.dart' as Api;
import 'components/responsive.dart';
class InventoryInoutController extends GetxController {
@ -192,20 +191,12 @@ class InventoryInoutController extends GetxController {
Future<void> showInventoryInoutCreateDialog(String inOrOut) async {
final isTablet = Responsive.isTablet(Get.context!);
isTablet
? showGeneralDialog(
context: Get.overlayContext!,
? Get.generalDialog(
barrierLabel: "CreateInventoryInout",
barrierDismissible: true,
transitionDuration: const Duration(milliseconds: 400),
pageBuilder: (_, __, ___) {
return Container(
margin: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(100),
vertical: ScreenAdaper.height(60)),
child: ClipRRect(
borderRadius: BorderRadius.circular(30),
child: Material(child: NewInventoryInout())),
);
return NewInventoryInout();
},
transitionBuilder: (_, anim, __, child) {
Tween<Offset> tween;
@ -217,7 +208,7 @@ class InventoryInoutController extends GetxController {
child: child,
);
},
).then((value) => Get.delete<NewInventoryInoutController>())
)
: showModalBottomSheet(
elevation: 0,
isScrollControlled: true,
@ -229,6 +220,14 @@ class InventoryInoutController extends GetxController {
);
}
Future<void> getInoutHistory() async {
final res = await Api.getInventoryInout({});
// final List<Map<String, Object?>> queryResult =
// await dbClient!.query('Tasks');
// return queryResult.map((e) => TaskModel.fromMap(e)).toList();
}
getTasks() async {
db.getData().then((value) {
model.value = value;
@ -262,14 +261,14 @@ class InventoryInoutController extends GetxController {
}
onMoveNextPage() {
if (currentIndex.value < 7) {
setIndex(currentIndex.value + 1);
if (currentIndex.value > 0) {
setIndex(currentIndex.value - 1);
}
}
onMoveBack() {
if (currentIndex.value > 0) {
setIndex(currentIndex.value - 1);
if (currentIndex.value < 20) {
setIndex(currentIndex.value + 1);
}
}

View File

@ -17,6 +17,7 @@ class LandingPage extends StatelessWidget {
child: Stack(children: [
const BackColors(),
SafeArea(
top: false,
child: Obx(() => Scaffold(
floatingActionButtonLocation:
FloatingActionButtonLocation.centerDocked,

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:get/get.dart';
@ -22,74 +24,32 @@ class NewInventoryInout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: ScreenAdaper.height(800),
width: Get.width,
decoration: const BoxDecoration(
color: Colors.white,
),
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(
ScreenAdaper.sp(30),
return SafeArea(
top: false, // false以避免保留顶部状态栏的空间
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(ScreenAdaper.height(
kToolbarHeight)), // 使kToolbarHeightAppBar的推荐高度
child: AppBar(
title: const Text('出入库登记'),
),
topRight: Radius.circular(ScreenAdaper.sp(30))),
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
//
// FocusScope.of(Get.context!).unfocus();
},
child: Stack(
children: [
Positioned.fill(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildTitle(),
SingleChildScrollView(
child: buildForm(),
),
Spacer(),
Obx(() => GradientButton(
onPressed: () => {controller.insertTask(context)},
isLoading: controller.loading.value,
))
],
))
],
),
)),
);
}
Widget buildTitle() {
return Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: AppTheme.dividerColor))),
padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(5), horizontal: ScreenAdaper.width(20)),
child: Row(
children: [
Text(
TextEnum.inventoryInOutDialogTitle,
style: TextStyle(
fontSize: ScreenAdaper.sp(25),
fontWeight: FontWeight.w700,
),
),
const Spacer(),
IconButton(
onPressed: () {
Get.back();
},
icon: Icon(
Icons.close,
size: ScreenAdaper.sp(30),
))
],
),
);
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
),
child: Column(
children: [
buildForm(),
Obx(() => GradientButton(
onPressed: () => {controller.create()},
isLoading: controller.loading.value,
))
],
),
))));
}
Widget buildForm() {
@ -114,25 +74,27 @@ class NewInventoryInout extends StatelessWidget {
),
Row(
children: [
Expanded(flex: 1, child: buildQuantity()),
SizedBox(
width: ScreenAdaper.width(formHorizontalGap),
),
Expanded(flex: 1, child: buildDatePicker()),
SizedBox(
width: ScreenAdaper.width(formHorizontalGap),
),
Expanded(flex: 1, child: buildQuantity()),
Expanded(flex: 1, child: buildAgent()),
],
),
SizedBox(
height: ScreenAdaper.height(formVerticalGap),
),
Row(
children: [Expanded(child: buildAgent())],
),
SizedBox(
height: ScreenAdaper.height(formVerticalGap),
),
Row(
children: [Expanded(child: buildRemark())],
),
SizedBox(
height: ScreenAdaper.height(formVerticalGap),
),
buildImageUploadQueue()
];
final child = Column(
@ -239,8 +201,120 @@ class NewInventoryInout extends StatelessWidget {
return ZtTextInput(
textController: controller.remarkTextController,
labelText: '备注',
isRequired: true,
isTextArea: true,
);
}
///
Widget buildImageUploadQueue() {
return Container(
padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(20),
horizontal: ScreenAdaper.width(20)),
alignment: Alignment.center,
child: Column(
children: [
Text(
'*请拍照上传产品照片',
style: TextStyle(
fontSize: ScreenAdaper.sp(30),
color: AppTheme.secondPrimaryColor),
),
SizedBox(
height: ScreenAdaper.height(10),
),
Obx(() => SingleChildScrollView(
controller: controller.uploadScrollController,
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...controller.uploadImgFilesPath
.map((String path) => builderImagePreview(path))
.toList(),
buildImageUploader()
],
),
)),
],
),
);
}
/// Item
Widget builderImagePreview(String path) {
return Stack(
children: [
Container(
margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
width: ScreenAdaper.width(200),
height: ScreenAdaper.width(200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(width: 1.0, color: AppTheme.dividerColor),
// color: AppTheme.primaryColor,
),
child: Center(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
image: DecorationImage(
fit: BoxFit.cover,
image: FileImage(File(path)),
),
),
),
),
),
Positioned(
top: ScreenAdaper.height(5),
right: ScreenAdaper.width(5),
child: GestureDetector(
onTap: () {
controller.uploadImgFilesPath.remove(path);
},
child: Icon(
Icons.close,
shadows: [
Shadow(
color: Colors.black,
offset: Offset(1, 1),
blurRadius: 3,
),
Shadow(
color: Colors.black,
offset: Offset(-1, -1),
blurRadius: 3,
),
],
size: ScreenAdaper.sp(40),
color: AppTheme.nearlyWhite,
),
))
],
);
}
//
Widget buildImageUploader() {
return GestureDetector(
onTap: () {
controller.photoPicker();
},
child: Container(
margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
width: ScreenAdaper.width(200),
height: ScreenAdaper.width(200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(width: 1.0, color: AppTheme.dividerColor),
// color: AppTheme.primaryColor,
),
child: Center(
child: Icon(
Icons.add_a_photo_rounded,
size: ScreenAdaper.sp(60),
color: AppTheme.primaryColor,
))));
}
}

View File

@ -1,7 +1,9 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.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/product.model.dart';
@ -10,6 +12,10 @@ 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/apis/api.dart' as Api;
import 'package:sk_base_mobile/util/date.util.dart';
import 'package:sk_base_mobile/util/loading_util.dart';
import 'package:sk_base_mobile/util/logger_util.dart';
import 'package:sk_base_mobile/util/media_util.dart';
import 'package:sk_base_mobile/util/photo_picker_util.dart';
import 'package:sk_base_mobile/util/snack_bar.util.dart';
class NewInventoryInoutController extends GetxController {
@ -38,6 +44,8 @@ class NewInventoryInoutController extends GetxController {
final quantityTextController = TextEditingController();
final agentTextController = TextEditingController();
final remarkTextController = TextEditingController();
final uploadImgFilesPath = <String>[].obs;
final uploadScrollController = ScrollController();
Map<String, dynamic> payload = {};
Future<List<ProjectModel>> getProjects({String? keyword}) async {
@ -60,6 +68,82 @@ class NewInventoryInoutController extends GetxController {
return products;
}
@override
onReady() {
super.onReady();
dateTextController.text = DateUtil.format(DateTime.now());
}
///
create() {
if (payload['projectId'] == null) {
SnackBarUtil().info(
'项目不能为空',
);
return;
}
if (payload['productId'] == null) {
SnackBarUtil().info(
'产品不能为空',
);
return;
}
if (payload['time'] == null) {
SnackBarUtil().info(
'时间不能为空',
);
return;
}
payload['quantity'] = quantityTextController.text;
if (payload['quantity'].isEmpty) {
SnackBarUtil().info(
'数量不能为空',
);
return;
}
payload['agent'] = agentTextController.text;
if (payload['agent'].isEmpty) {
SnackBarUtil().info(
'经办人不能为空',
);
return;
}
LoggerUtil().info(payload);
}
Future<void> photoPicker() async {
XFile? pickedFile = await MediaUtil().getImageFromCamera();
if (pickedFile != null) {
uploadImgFilesPath.add(pickedFile.path);
Future.delayed(const Duration(milliseconds: 100), () {
//
uploadScrollController.animateTo(
uploadScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
});
}
// await PhotoPickerUtil().showPicker(callback: (XFile pickedFile) async {
// await LoadingUtil.to.show(status: '上传中...');
// try {
// String? filename = await MediaUtil().uploadImg(File(pickedFile.path));
// if (filename.isNotEmpty) {
// final res = await Api.updateAvatar(filename);
// if (res.data != null) {
// SnackBarUtil().success('上传成功');
// }
// }
// // uploadImgFilePath(pickedFile.path);
// Get.back();
// } catch (e) {
// SnackBarUtil().error('上传失败,请重试');
// } finally {
// await LoadingUtil.to.dismiss();
// }
// });
}
picStartTime(BuildContext context) async {
var picker =
await showTimePicker(context: context, initialTime: TimeOfDay.now());

View File

@ -48,25 +48,33 @@ class WorkBenchPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(children: [
Container(
padding: EdgeInsets.all(ScreenAdaper.width(20)),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: works.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: Get.width > 800 ? 5 : 3,
crossAxisSpacing: ScreenAdaper.width(20),
mainAxisSpacing: ScreenAdaper.height(20),
childAspectRatio: 1.0),
itemBuilder: (BuildContext context, int index) {
return buildCard(index);
},
return Scaffold(
appBar: AppBar(
leading: const SizedBox(),
title: const Text(
'工作台',
),
)
]));
),
body: SingleChildScrollView(
child: Column(children: [
Container(
padding: EdgeInsets.all(ScreenAdaper.width(20)),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: works.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: Get.width > 800 ? 5 : 3,
crossAxisSpacing: ScreenAdaper.width(20),
mainAxisSpacing: ScreenAdaper.height(20),
childAspectRatio: 1.0),
itemBuilder: (BuildContext context, int index) {
return buildCard(index);
},
),
)
])),
);
}
Widget buildCard(int index) {

View File

@ -7,25 +7,25 @@ import 'package:sk_base_mobile/util/media_util.dart';
import 'package:sk_base_mobile/app_theme.dart';
class PhotoPickerUtil {
showPicker({Function? callback}) async {
showPicker({Function? callback, String title = '请选择上传方式'}) async {
return showCupertinoModalPopup(
context: Get.context!,
builder: (BuildContext ctx) {
return CupertinoActionSheet(
title: const Text('Change avatar'),
title: Text(title),
cancelButton: CupertinoActionSheetAction(
onPressed: () {
Get.back();
},
child: const Text(
'Cancel',
'取消',
style: TextStyle(color: Color(0xffcdcdcd)),
)),
actions: ['Photography', 'Album']
actions: ['拍照', '相册']
.map((item) => CupertinoActionSheetAction(
onPressed: () async {
Get.back();
XFile? pickedFile = item == 'Photography'
XFile? pickedFile = item == '拍照'
? await MediaUtil().getImageFromCamera()
: await MediaUtil().getImageFromGallery();
if (pickedFile != null) {

View File

@ -8,10 +8,15 @@ class ZtDatePicker extends StatelessWidget {
final TextEditingController textController;
final VoidCallback? onClear;
final Function? onDateSelected;
final bool isRequired;
final String labelText;
const ZtDatePicker({
super.key,
this.onClear,
this.onDateSelected,
this.labelText = '日期',
this.isRequired = false,
required this.textController,
});
@ -41,16 +46,19 @@ class ZtDatePicker extends StatelessWidget {
)
: const SizedBox(),
hintText: '请选择',
label: const Row(
label: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (isRequired)
Text(
"*",
style: TextStyle(
color: Colors.red, fontSize: ScreenAdaper.sp(30)),
),
Text(
"*",
style: TextStyle(color: Colors.red),
),
Text(
'日期',
labelText,
style: TextStyle(fontSize: ScreenAdaper.sp(30)),
),
])),
keyboardType: TextInputType.none,
@ -63,6 +71,7 @@ class ZtDatePicker extends StatelessWidget {
cancelText: '取消',
firstDate: DateTime(2000, 1, 1),
lastDate: DateTime.now().add(const Duration(days: 7)));
if (onDateSelected != null) {
onDateSelected!(picker);
}

View File

@ -0,0 +1,101 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/util/logger_util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class ZtBaseDatePicker extends StatelessWidget {
final Function? onDateTimeChanged;
final yearController =
FixedExtentScrollController(initialItem: DateTime.now().year - 2000);
final monthController =
FixedExtentScrollController(initialItem: DateTime.now().month - 1);
final dayController =
FixedExtentScrollController(initialItem: DateTime.now().day - 1);
ZtBaseDatePicker({super.key, this.onDateTimeChanged});
@override
Widget build(BuildContext context) {
return Container(
height: ScreenAdaper.height(250),
color: Colors.white,
child: Column(
children: [
Row(
children: [
TextButton(
onPressed: () {
Get.back();
},
child: Text(
'取消',
style: TextStyle(fontSize: ScreenAdaper.sp(25)),
)),
const Spacer(),
TextButton(
onPressed: () {
Get.back();
final year = 2000 + yearController.selectedItem;
String month = '${monthController.selectedItem + 1}';
if (int.parse(month) < 10) {
month = '0$month';
}
final day = dayController.selectedItem + 1;
if (onDateTimeChanged != null) {
onDateTimeChanged!('$year-$month-$day');
}
LoggerUtil().info('$year-$month-$day');
},
child: Text(
'确定',
style: TextStyle(fontSize: ScreenAdaper.sp(25)),
))
],
),
Expanded(
child: Row(
children: <Widget>[
Expanded(
child: CupertinoPicker(
scrollController: yearController,
itemExtent: ScreenAdaper.height(60),
onSelectedItemChanged: (int index) {
//
},
children: List<Widget>.generate(DateTime.now().year - 1999,
(int index) {
return Center(child: Text('${2000 + index}'));
}),
),
),
Expanded(
child: CupertinoPicker(
scrollController: monthController,
itemExtent: ScreenAdaper.height(60),
onSelectedItemChanged: (int index) {
//
},
children: List<Widget>.generate(12, (int index) {
return Center(child: Text('${index + 1}'));
}),
),
),
Expanded(
child: CupertinoPicker(
scrollController: dayController,
itemExtent: ScreenAdaper.height(60),
onSelectedItemChanged: (int index) {
//
},
children: List<Widget>.generate(31, (int index) {
return Center(child: Text('${index + 1}'));
}),
),
),
],
))
],
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/bg_color.dart';
@ -26,20 +27,28 @@ class ZtNumberInput extends StatelessWidget {
FocusScope.of(context).unfocus();
},
onTap: onTap ?? () {},
textAlign: TextAlign.center,
style: TextStyle(fontSize: ScreenAdaper.sp(30)),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly //
],
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: ScreenAdaper.height(18)),
floatingLabelBehavior: FloatingLabelBehavior.always,
label: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (isRequired)
const Text(
Text(
"*",
style: TextStyle(color: Colors.red),
style: TextStyle(
color: Colors.red, fontSize: ScreenAdaper.sp(30)),
),
Text(
labelText,
style: TextStyle(fontSize: ScreenAdaper.sp(30)),
),
]),
focusedBorder: OutlineInputBorder(

View File

@ -67,12 +67,15 @@ class ZtSearchSelect extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
if (isRequired)
const Text(
Text(
"*",
style: TextStyle(color: Colors.red),
style: TextStyle(
color: Colors.red,
fontSize: ScreenAdaper.sp(30)),
),
Text(
labelText,
style: TextStyle(fontSize: ScreenAdaper.sp(30)),
),
])));
},

View File

@ -27,7 +27,7 @@ class ZtTextInput extends StatelessWidget {
onTapOutside: (event) {
FocusScope.of(context).unfocus();
},
maxLines: isTextArea ? null : 1, //
maxLines: isTextArea ? 3 : 1, //
onTap: onTap ?? () {},
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
@ -36,12 +36,14 @@ class ZtTextInput extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
if (isRequired)
const Text(
Text(
"*",
style: TextStyle(color: Colors.red),
style: TextStyle(
color: Colors.red, fontSize: ScreenAdaper.sp(30)),
),
Text(
labelText,
style: TextStyle(fontSize: ScreenAdaper.sp(30)),
),
]),
focusedBorder: OutlineInputBorder(

View File

@ -41,7 +41,7 @@ class GradientButton extends StatelessWidget {
: Text(
buttonText,
style: TextStyle(
color: Colors.white,
color: AppTheme.nearlyWhite,
fontWeight: FontWeight.bold,
fontSize: ScreenAdaper.sp(25),
),

30
test/widget_test.dart Normal file
View File

@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sk_base_mobile/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}

BIN
web/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

BIN
web/icons/Icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
web/icons/Icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

59
web/index.html Normal file
View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="sk_base_mobile">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>sk_base_mobile</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
const serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
});
</script>
</body>
</html>

35
web/manifest.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "sk_base_mobile",
"short_name": "sk_base_mobile",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}