feat: develop common widget. Hr management feature

This commit is contained in:
louis 2024-04-08 13:50:23 +08:00
parent 1befdcb786
commit 93a30355cd
24 changed files with 529 additions and 101 deletions

View File

@ -15,7 +15,7 @@ class AppTheme {
static const Color nearlyWhite = Color(0xFFFEFEFE); static const Color nearlyWhite = Color(0xFFFEFEFE);
static const Color black = Color(0xFF000000); static const Color black = Color(0xFF000000);
static const Color nearlyBlack = Color(0xFF213333); static const Color nearlyBlack = Color(0xFF213333);
static const Color grey = Color.fromARGB(255, 98, 101, 102); static const Color grey = Color.fromARGB(255, 138, 138, 138);
static const Color snackbarErrorBackgroudColor = Colors.red; static const Color snackbarErrorBackgroudColor = Colors.red;
static const Color snackbarSuccessBackgroudColor = Colors.green; static const Color snackbarSuccessBackgroudColor = Colors.green;
static const Color snackbarWarningBackgroudColor = Colors.orange; static const Color snackbarWarningBackgroudColor = Colors.orange;
@ -25,6 +25,9 @@ class AppTheme {
static const Color notActiveNavigationBarColor = Colors.grey; static const Color notActiveNavigationBarColor = Colors.grey;
static const Color dangerColor = Colors.red; static const Color dangerColor = Colors.red;
static const Color dividerColor = Color.fromARGB(255, 224, 224, 224); static const Color dividerColor = Color.fromARGB(255, 224, 224, 224);
static const Color appbarBgColor = AppTheme.primaryColor;
static const Color scaffoldBackgroundColor = Color(0xFFf5f8ff);
static const Color inputFillColor = Color(0xFFf5f8ff);
} }
final theme = ThemeData( final theme = ThemeData(
@ -85,7 +88,7 @@ final theme = ThemeData(
const ProgressIndicatorThemeData(color: AppTheme.primaryColor), const ProgressIndicatorThemeData(color: AppTheme.primaryColor),
dividerColor: AppTheme.dividerColor, dividerColor: AppTheme.dividerColor,
cardColor: AppTheme.white, cardColor: AppTheme.white,
scaffoldBackgroundColor: AppTheme.nearlyWhite, scaffoldBackgroundColor: AppTheme.scaffoldBackgroundColor,
bottomNavigationBarTheme: BottomNavigationBarThemeData( bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: AppTheme.nearlyWhite, backgroundColor: AppTheme.nearlyWhite,
unselectedLabelStyle: TextStyle(fontSize: ScreenAdaper.height(20)), unselectedLabelStyle: TextStyle(fontSize: ScreenAdaper.height(20)),
@ -103,7 +106,7 @@ final theme = ThemeData(
appBarTheme: AppBarTheme( appBarTheme: AppBarTheme(
centerTitle: true, centerTitle: true,
iconTheme: const IconThemeData(color: Colors.white), iconTheme: const IconThemeData(color: Colors.white),
backgroundColor: AppTheme.primaryColor, backgroundColor: AppTheme.appbarBgColor,
titleTextStyle: TextStyle( titleTextStyle: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: ScreenAdaper.height(30), fontSize: ScreenAdaper.height(30),
@ -114,6 +117,8 @@ final theme = ThemeData(
color: AppTheme.primaryColor, color: AppTheme.primaryColor,
fontSize: ScreenAdaper.height(30), fontSize: ScreenAdaper.height(30),
), ),
fillColor: AppTheme.inputFillColor,
filled: true,
labelStyle: TextStyle( labelStyle: TextStyle(
fontSize: ScreenAdaper.height(25), fontSize: ScreenAdaper.height(25),
), ),

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

@ -1,4 +1,5 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sk_base_mobile/screens/hr_manage/hr_manage.dart';
import 'package:sk_base_mobile/screens/inventory/inventory.dart'; import 'package:sk_base_mobile/screens/inventory/inventory.dart';
import 'package:sk_base_mobile/screens/login/login.dart'; import 'package:sk_base_mobile/screens/login/login.dart';
import 'package:sk_base_mobile/screens/sale_quotation/sale_quotation.dart'; import 'package:sk_base_mobile/screens/sale_quotation/sale_quotation.dart';
@ -12,12 +13,14 @@ class RouteConfig {
static const String userinfo = '/userinfo'; static const String userinfo = '/userinfo';
static const String inventory = '/inventory'; static const String inventory = '/inventory';
static const String saleQuotation = '/sale_quotation'; static const String saleQuotation = '/sale_quotation';
static const String hrManage = '/hr_manage';
static final List<GetPage> getPages = [ static final List<GetPage> getPages = [
GetPage(name: login, page: () => LoginScreen()), GetPage(name: login, page: () => LoginScreen()),
GetPage(name: home, page: () => LandingPage()), GetPage(name: home, page: () => LandingPage()),
GetPage(name: userinfo, page: () => UserInfoPage()), GetPage(name: userinfo, page: () => UserInfoPage()),
GetPage(name: inventory, page: () => const InventoryPage()), GetPage(name: inventory, page: () => const InventoryPage()),
GetPage(name: saleQuotation, page: () => SaleQuotationPage()) GetPage(name: saleQuotation, page: () => SaleQuotationPage()),
GetPage(name: hrManage, page: () => HrManagePage())
]; ];
} }

View File

@ -8,6 +8,7 @@ import 'package:sk_base_mobile/constants/cache_key.dart';
import 'package:sk_base_mobile/services/app_info.service.dart'; import 'package:sk_base_mobile/services/app_info.service.dart';
import 'package:sk_base_mobile/services/dio.service.dart'; import 'package:sk_base_mobile/services/dio.service.dart';
import 'package:sk_base_mobile/services/storage.service.dart'; import 'package:sk_base_mobile/services/storage.service.dart';
import 'package:timeago/timeago.dart';
import 'store/store.dart'; import 'store/store.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart';
@ -27,7 +28,7 @@ class Global {
setSystemUi(); setSystemUi();
/// ///
timeago.setLocaleMessages('en', timeago.EnMessages()); timeago.setLocaleMessages('zh_cn', MyCustomMessages());
/// Loading工具 /// Loading工具
await Get.putAsync<LoadingUtil>(() => LoadingUtil().init()); await Get.putAsync<LoadingUtil>(() => LoadingUtil().init());
@ -69,3 +70,39 @@ class Global {
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
} }
} }
// my_custom_messages.dart
class MyCustomMessages implements LookupMessages {
@override
String prefixAgo() => '';
@override
String prefixFromNow() => '';
@override
String suffixAgo() => '';
@override
String suffixFromNow() => '';
@override
String lessThanOneMinute(int seconds) => '现在';
@override
String aboutAMinute(int minutes) => '$minutes分钟';
@override
String minutes(int minutes) => '$minutes分钟';
@override
String aboutAnHour(int minutes) => '$minutes分钟';
@override
String hours(int hours) => '$hours小时';
@override
String aDay(int hours) => '$hours小时';
@override
String days(int days) => '$days天';
@override
String aboutAMonth(int days) => '$days天';
@override
String months(int months) => '$months月';
@override
String aboutAYear(int year) => '$year年';
@override
String years(int years) => '$years年';
@override
String wordSeparator() => ' ';
}

View File

@ -0,0 +1,295 @@
import 'package:flutter/material.dart';
import 'package:get/get.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/constants/bg_color.dart';
import 'package:sk_base_mobile/models/user_info.model.dart';
import 'package:sk_base_mobile/util/date.util.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/core/sk_ink.dart';
import 'package:sk_base_mobile/widgets/core/sk_tag.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/sk_appbar.dart';
class HrManagePage extends StatelessWidget {
final controller = Get.put(HrManageController());
HrManagePage({super.key});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
//
FocusScope.of(context).requestFocus(FocusNode());
},
child: Scaffold(
backgroundColor: Color(0xFFe9f0fd),
appBar: SkAppbar(
backgroundColor: AppTheme.nearlyWhite,
iconAndTextColor: AppTheme.black,
title: '人事管理',
),
body: buildBody(),
));
}
Widget buildBody() {
return Column(
children: [
buildSearchBar(),
Expanded(
child: Container(
margin: EdgeInsets.symmetric(
horizontal: ScreenAdaper.height(defaultPadding),
vertical: ScreenAdaper.height(defaultPadding)),
child: Obx(
() => SmartRefresher(
enablePullDown: true,
enablePullUp: true,
controller: controller.refreshController,
onLoading: controller.onLoading,
onRefresh: controller.onRefresh,
child: controller.list.isEmpty
? const Center(
child: Empty(text: '暂无数据'),
)
: ListView.builder(
itemBuilder: (context, index) {
return buildUserCard(index);
},
itemCount: controller.list.length,
)),
)),
)
],
);
}
Widget buildSearchBar() {
final doSearch = debouncer((String value) {
controller.searchKey.value = value;
controller.onRefresh();
}, delayTime: 500);
return Container(
color: AppTheme.nearlyWhite,
padding: EdgeInsets.only(
right: ScreenAdaper.width(20),
left: ScreenAdaper.width(20),
bottom: ScreenAdaper.height(20)),
child: Row(children: [
Expanded(
child: SizedBox(
height: ScreenAdaper.height(70),
child: SkTextInput(
textController: controller.searchBarTextConroller,
onChanged: (value) => doSearch(value),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
prefix: Icon(
Icons.search,
color: AppTheme.nearlyBlack,
size: ScreenAdaper.height(40),
), // searchBarController有值时不显示
suffixIcon: Obx(() => controller.searchKey.value.isEmpty
? const SizedBox()
: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
controller.searchKey.value = '';
controller.searchBarTextConroller.clear();
doSearch('');
},
)),
hint: '查询员工',
isDense: true,
contentPadding:
EdgeInsets.symmetric(vertical: ScreenAdaper.height(10)),
),
)),
SizedBox(
width: ScreenAdaper.width(10),
),
SkInk(
border: Border.all(color: AppTheme.grey.withOpacity(0.8)),
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)),
onTap: () {},
child: SizedBox(
width: ScreenAdaper.height(65),
height: ScreenAdaper.height(65),
child: Icon(
Icons.filter_list_sharp,
size: ScreenAdaper.height(50),
),
),
)
]),
);
}
Widget buildUserCard(int index) {
return Container(
padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.height(defaultPadding),
vertical: ScreenAdaper.height(defaultPadding)),
margin: EdgeInsets.only(bottom: ScreenAdaper.height(defaultPadding)),
decoration: BoxDecoration(
color: AppTheme.white,
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
child: Column(
children: [
Row(
children: [
//
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)),
child: Image(
height: ScreenAdaper.height(80),
image: NetworkImage(
'https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=1743369777')),
),
SizedBox(
width: ScreenAdaper.height(defaultPadding),
),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${controller.list[index].nickname}',
style: TextStyle(
fontSize: ScreenAdaper.height(30),
fontWeight: FontWeight.w600)),
SizedBox(
height: ScreenAdaper.height(5),
), // role
Text('${controller.list[index].dept?.name}',
style: TextStyle(color: AppTheme.grey)),
SizedBox(
height: ScreenAdaper.height(5),
),
Container(
child: Wrap(
spacing: ScreenAdaper.height(5),
runSpacing: ScreenAdaper.height(5),
children: [
...controller.list[index].roles.map((e) => SkTag(
text: '${e.name}', color: AppTheme.primaryColorLight))
],
)),
],
)),
/// action
///
buildActionButton(
onTap: () {},
icon: Icons.phone,
),
SizedBox(
width: ScreenAdaper.height(defaultPadding),
),
///
buildActionButton(
onTap: () {},
icon: Icons.email_outlined,
),
SizedBox(
width: ScreenAdaper.height(defaultPadding),
),
///
buildActionButton(
onTap: () {},
icon: Icons.edit,
)
],
),
const Divider(),
Row(
children: [
Text(
'工龄: ${SkDateUtil.howLongAgo(controller.list[index].createdAt!)}'),
const Spacer(),
const SkTag(
text: '在职',
color: Colors.green,
)
],
)
],
),
);
}
Widget buildActionButton({
required Function() onTap,
required IconData icon,
}) {
return SkInk(
onTap: () {},
border: Border.all(color: AppTheme.grey.withOpacity(0.8)),
borderRadius: BorderRadius.circular(ScreenAdaper.sp(30)),
child: Container(
padding: EdgeInsets.all(ScreenAdaper.height(5)),
child: Icon(
icon,
size: ScreenAdaper.height(35),
),
),
);
}
}
class HrManageController extends GetxController {
RxList<UserInfoModel> list = RxList([]);
RxString searchKey = ''.obs;
final searchBarTextConroller = TextEditingController();
RefreshController refreshController = RefreshController(initialRefresh: true);
int page = 1;
int limit = 15;
int total = 0;
Future<List<UserInfoModel>> getData({bool isRefresh = false}) async {
if (isRefresh == true) {
page = 1;
} else {
page++;
}
final res = await Api.getUsers({
'page': page,
'pageSize': 15,
'keyword': searchKey.value,
});
List<UserInfoModel> newList =
res.data!.items.map((e) => UserInfoModel.fromJson(e)).toList();
isRefresh == true ? list.assignAll(newList) : list.addAll(newList);
return newList;
}
Future<void> onRefresh() async {
await getData(isRefresh: true).then((_) {
refreshController.refreshCompleted(resetFooterState: true);
}).catchError((_) {
refreshController.refreshFailed();
});
}
Future<void> onLoading() async {
await getData().then((_) {
if (_.isEmpty) {
refreshController.loadNoData();
} else {
refreshController.loadComplete();
}
}).catchError((_) {
refreshController.loadFailed();
});
}
}

View File

@ -14,7 +14,7 @@ class Dates extends StatelessWidget {
children: [ children: [
Obx( Obx(
() => Text( () => Text(
DateUtil.getMonth( SkDateUtil.getMonth(
controller.endTime.value.add(Duration(days: -index))), controller.endTime.value.add(Duration(days: -index))),
style: TextStyle( style: TextStyle(
color: controller.currentIndex.value == index color: controller.currentIndex.value == index
@ -27,7 +27,7 @@ class Dates extends StatelessWidget {
), ),
Obx( Obx(
() => Text( () => Text(
DateUtil.getDate( SkDateUtil.getDate(
controller.endTime.value.add(Duration(days: -index))), controller.endTime.value.add(Duration(days: -index))),
style: TextStyle( style: TextStyle(
color: controller.currentIndex.value == index color: controller.currentIndex.value == index
@ -40,7 +40,7 @@ class Dates extends StatelessWidget {
), ),
Obx( Obx(
() => Text( () => Text(
DateUtil.getDay( SkDateUtil.getDay(
controller.endTime.value.add(Duration(days: -index))), controller.endTime.value.add(Duration(days: -index))),
style: TextStyle( style: TextStyle(
color: controller.currentIndex.value == index color: controller.currentIndex.value == index

View File

@ -109,7 +109,7 @@ class InventoryInoutCard extends StatelessWidget {
), ),
), ),
Text( Text(
'${DateUtil.format(controller.list[ind][index].time!, formats: [ '${SkDateUtil.format(controller.list[ind][index].time!, formats: [
'HH', 'HH',
':', ':',
"nn" "nn"

View File

@ -62,7 +62,7 @@ class InventoryInoutInfo extends StatelessWidget {
], ],
buildListItem( buildListItem(
leading: '出入库时间', leading: '出入库时间',
trailing: DateUtil.format( trailing: SkDateUtil.format(
controller.inventoryInoutInfo.value!.time!, controller.inventoryInoutInfo.value!.time!,
formats: [ formats: [
'yyyy', 'yyyy',

View File

@ -247,8 +247,8 @@ class InventoryInoutController extends GetxController {
Future<List<InventoryInOutModel>> getInoutHistory() async { Future<List<InventoryInOutModel>> getInoutHistory() async {
loading.value = true; loading.value = true;
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
final selectedDate = final selectedDate = SkDateUtil.format(
DateUtil.format(endTime.value.add(Duration(days: -currentIndex.value))); endTime.value.add(Duration(days: -currentIndex.value)));
try { try {
final res = await Api.getInventoryInout({ final res = await Api.getInventoryInout({
'time': [selectedDate, selectedDate] 'time': [selectedDate, selectedDate]
@ -290,7 +290,7 @@ class InventoryInoutController extends GetxController {
} }
getDateAccordingTabs(int value) { getDateAccordingTabs(int value) {
return DateUtil.format( return SkDateUtil.format(
endTime.value.add(Duration(days: -(daysNum - value)))); endTime.value.add(Duration(days: -(daysNum - value))));
} }
@ -300,7 +300,7 @@ class InventoryInoutController extends GetxController {
RxList<InventoryInOutModel> tempList1 = <InventoryInOutModel>[].obs; RxList<InventoryInOutModel> tempList1 = <InventoryInOutModel>[].obs;
tempList1.clear(); tempList1.clear();
for (int j = 0; j < model.length; j++) { for (int j = 0; j < model.length; j++) {
final sourceDateStr = DateUtil.format(model[j].time); final sourceDateStr = SkDateUtil.format(model[j].time);
final currentDateStr = getDateAccordingTabs(i); final currentDateStr = getDateAccordingTabs(i);
if (sourceDateStr == currentDateStr) { if (sourceDateStr == currentDateStr) {
tempList1.add(model[j]); tempList1.add(model[j]);

View File

@ -56,7 +56,7 @@ class MineSettingsPage extends StatelessWidget {
EdgeInsets.symmetric(vertical: ScreenAdaper.width(10)), EdgeInsets.symmetric(vertical: ScreenAdaper.width(10)),
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
await AppInfoService.to.checkVersion(); await AppInfoService.to.checkVersion(forceCheck: true);
}, },
child: Container( child: Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(

View File

@ -83,18 +83,16 @@ class AgentSearch extends StatelessWidget {
controller: controller.refreshController, controller: controller.refreshController,
onLoading: controller.onLoading, onLoading: controller.onLoading,
onRefresh: controller.onRefresh, onRefresh: controller.onRefresh,
child: controller.refreshController.isLoading child: controller.list.isEmpty
? const SizedBox() ? const Center(
: controller.list.isEmpty child: Empty(text: '暂无数据'),
? const Center( )
child: Empty(text: '暂无数据'), : ListView.separated(
) separatorBuilder: (context, index) => const Divider(
: ListView.separated( color: AppTheme.dividerColor,
separatorBuilder: (context, index) => const Divider( ),
color: AppTheme.dividerColor, itemCount: controller.list.length,
), itemBuilder: (_, index) => buildItem(index))));
itemCount: controller.list.length,
itemBuilder: (_, index) => buildItem(index))));
} }
Widget buildItem(int index) { Widget buildItem(int index) {

View File

@ -283,7 +283,7 @@ class NewInventoryInout extends StatelessWidget {
}, },
onDateSelected: (date) { onDateSelected: (date) {
if (date != null) { if (date != null) {
controller.dateTextController.text = DateUtil.format(date); controller.dateTextController.text = SkDateUtil.format(date);
controller.payload['time'] = controller.dateTextController.text; controller.payload['time'] = controller.dateTextController.text;
} }
}, },

View File

@ -74,8 +74,8 @@ class NewInventoryInoutController extends GetxController {
@override @override
onReady() { onReady() {
super.onReady(); super.onReady();
dateTextController.text = DateUtil.format(DateTime.now()); dateTextController.text = SkDateUtil.format(DateTime.now());
payload['time'] = DateUtil.format(DateTime.now()); payload['time'] = SkDateUtil.format(DateTime.now());
payload['inOrOut'] = inOrOut; payload['inOrOut'] = inOrOut;
} }
@ -136,7 +136,7 @@ class NewInventoryInoutController extends GetxController {
// return; // return;
// } // }
payload['time'] = DateUtil.format(DateTime.now(), formats: [ payload['time'] = SkDateUtil.format(DateTime.now(), formats: [
'yyyy', 'yyyy',
'-', '-',
'mm', 'mm',
@ -274,7 +274,7 @@ class NewInventoryInoutController extends GetxController {
await showTimePicker(context: context, initialTime: TimeOfDay.now()); await showTimePicker(context: context, initialTime: TimeOfDay.now());
if (picker != null) { if (picker != null) {
startTime.value = startTime.value =
'${DateUtil.addPrefix(picker.hourOfPeriod.toString())}:${DateUtil.addPrefix(picker.minute.toString())}:${picker.period.name.toUpperCase()}'; '${SkDateUtil.addPrefix(picker.hourOfPeriod.toString())}:${SkDateUtil.addPrefix(picker.minute.toString())}:${picker.period.name.toUpperCase()}';
} }
} }
@ -283,7 +283,7 @@ class NewInventoryInoutController extends GetxController {
await showTimePicker(context: context, initialTime: TimeOfDay.now()); await showTimePicker(context: context, initialTime: TimeOfDay.now());
if (picker != null) { if (picker != null) {
endTime.value = endTime.value =
'${DateUtil.addPrefix(picker.hourOfPeriod.toString())}:${DateUtil.addPrefix(picker.minute.toString())}:${picker.period.name.toUpperCase()}'; '${SkDateUtil.addPrefix(picker.hourOfPeriod.toString())}:${SkDateUtil.addPrefix(picker.minute.toString())}:${picker.period.name.toUpperCase()}';
} }
} }
@ -296,7 +296,7 @@ class NewInventoryInoutController extends GetxController {
if (picker != null) { if (picker != null) {
pickedDate = picker; pickedDate = picker;
selectedDate.value = selectedDate.value =
'${DateUtil.addPrefix(picker.day.toString())}/${DateUtil.addPrefix(picker.month.toString())}/${picker.year}'; '${SkDateUtil.addPrefix(picker.day.toString())}/${SkDateUtil.addPrefix(picker.month.toString())}/${picker.year}';
} }
} }

View File

@ -82,18 +82,16 @@ class SaleQuotationGroupSearch extends StatelessWidget {
controller: controller.refreshController, controller: controller.refreshController,
onLoading: controller.onLoading, onLoading: controller.onLoading,
onRefresh: controller.onRefresh, onRefresh: controller.onRefresh,
child: controller.refreshController.isLoading child: controller.list.isEmpty
? const SizedBox() ? const Center(
: controller.list.isEmpty child: Empty(text: '暂无数据'),
? const Center( )
child: Empty(text: '暂无数据'), : ListView.separated(
) separatorBuilder: (context, index) => const Divider(
: ListView.separated( color: AppTheme.dividerColor,
separatorBuilder: (context, index) => const Divider( ),
color: AppTheme.dividerColor, itemCount: controller.list.length,
), itemBuilder: (_, index) => buildItem(index))));
itemCount: controller.list.length,
itemBuilder: (_, index) => buildItem(index))));
} }
Widget buildItem(int index) { Widget buildItem(int index) {

View File

@ -7,7 +7,7 @@ class WorkBenchController extends GetxController {
WorkBenchModel(title: '库存', route: '/inventory', icon: 'inventory.svg'), WorkBenchModel(title: '库存', route: '/inventory', icon: 'inventory.svg'),
WorkBenchModel(title: '产品', route: '/product', icon: 'product.svg'), WorkBenchModel(title: '产品', route: '/product', icon: 'product.svg'),
WorkBenchModel(title: '合同', route: '/contract', icon: 'contract.svg'), WorkBenchModel(title: '合同', route: '/contract', icon: 'contract.svg'),
WorkBenchModel(title: '人事', route: '/hr', icon: 'hr.svg'), WorkBenchModel(title: '人事', route: '/hr_manage', icon: 'hr.svg'),
WorkBenchModel(title: '公车', route: '/vehicle', icon: 'vehicle.svg'), WorkBenchModel(title: '公车', route: '/vehicle', icon: 'vehicle.svg'),
WorkBenchModel(title: '任务', route: '/task_manage', icon: 'task_manage.svg'), WorkBenchModel(title: '任务', route: '/task_manage', icon: 'task_manage.svg'),
WorkBenchModel(title: '报表', route: '/report', icon: 'report.svg'), WorkBenchModel(title: '报表', route: '/report', icon: 'report.svg'),

View File

@ -112,7 +112,7 @@ class AppInfoService extends GetxService {
// } // }
// } // }
} }
Future<void> checkVersion() async { Future<void> checkVersion({forceCheck = false}) async {
final res = await Future.wait([ final res = await Future.wait([
Api.getSystemParamConfigByCode(SystemParamConfig.appVersion), Api.getSystemParamConfigByCode(SystemParamConfig.appVersion),
Api.getSystemParamConfigByCode(SystemParamConfig.isForceUpgrade) Api.getSystemParamConfigByCode(SystemParamConfig.isForceUpgrade)
@ -132,6 +132,8 @@ class AppInfoService extends GetxService {
), ),
// contentText: isForceUpgrade == '1' ? '此版本非常重要,强制更新' : '请更新', // contentText: isForceUpgrade == '1' ? '此版本非常重要,强制更新' : '请更新',
); );
} else if (forceCheck) {
SnackBarUtil().info('已经是最新版本');
} }
} }
} }

View File

@ -1,9 +1,10 @@
import 'package:date_format/date_format.dart'; import 'package:date_format/date_format.dart';
import 'package:timeago/timeago.dart' as timeago;
class DateUtil { class SkDateUtil {
/// YYYY-MM-DD /// YYYY-MM-DD
static String format(DateTime date, {List<String>? formats}) { static String format(DateTime date, {List<String>? formats}) {
return formatDate(date, formats ?? ['yyyy', '-', 'MM', '-', 'dd']); return formatDate(date, formats ?? ['yyyy', '-', 'mm', '-', 'dd']);
} }
/// ///
@ -33,4 +34,9 @@ class DateUtil {
} }
return string; return string;
} }
/// 使timeago距现在多久
static String howLongAgo(DateTime date) {
return timeago.format(date, locale: 'zh_cn'); //
}
} }

View File

@ -74,7 +74,7 @@ class SnackBarUtil {
borderRadius: 15, borderRadius: 15,
margin: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(
horizontal: ScreenAdaper.height(15), vertical: 0), horizontal: ScreenAdaper.height(15), vertical: 0),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 1),
dismissDirection: DismissDirection.horizontal, dismissDirection: DismissDirection.horizontal,
forwardAnimationCurve: Curves.fastLinearToSlowEaseIn, forwardAnimationCurve: Curves.fastLinearToSlowEaseIn,
reverseAnimationCurve: Curves.linearToEaseOut); reverseAnimationCurve: Curves.linearToEaseOut);

View File

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'package:sk_base_mobile/app_theme.dart';
class SkInk extends StatelessWidget {
final Widget? child;
final void Function()? onTap;
final BorderRadius? borderRadius;
final BoxBorder? border;
const SkInk(
{super.key, this.child, this.onTap, this.borderRadius, this.border});
@override
Widget build(BuildContext context) {
return Material(
child: Ink(
decoration: BoxDecoration(
border: border ?? Border.all(color: AppTheme.grey.withOpacity(0.8)),
borderRadius: borderRadius,
),
child:
InkWell(borderRadius: borderRadius, onTap: onTap, child: child)),
);
}
}

View File

@ -141,19 +141,17 @@ class SkMutilSearchMore<T extends BaseSearchMoreModel> extends StatelessWidget {
controller: controller.refreshController, controller: controller.refreshController,
onLoading: controller.onLoading, onLoading: controller.onLoading,
onRefresh: controller.onRefresh, onRefresh: controller.onRefresh,
child: controller.refreshController.isLoading child: controller.list.isEmpty
? const SizedBox() ? const Center(
: controller.list.isEmpty child: Empty(text: '暂无数据'),
? const Center( )
child: Empty(text: '暂无数据'), : ListView.separated(
) separatorBuilder: (context, index) => const Divider(
: ListView.separated( color: AppTheme.dividerColor,
separatorBuilder: (context, index) => const Divider( height: 1,
color: AppTheme.dividerColor, ),
height: 1, itemCount: controller.list.length,
), itemBuilder: (_, index) => buildItem(index))));
itemCount: controller.list.length,
itemBuilder: (_, index) => buildItem(index))));
} }
Widget buildItem(int index) { Widget buildItem(int index) {

View File

@ -125,20 +125,18 @@ class SkSingleSearchMore<T> extends StatelessWidget {
controller: controller.refreshController, controller: controller.refreshController,
onLoading: controller.onLoading, onLoading: controller.onLoading,
onRefresh: controller.onRefresh, onRefresh: controller.onRefresh,
child: controller.refreshController.isLoading child: controller.list.isEmpty
? const SizedBox() ? const Center(
: controller.list.isEmpty child: Empty(text: '暂无数据'),
? const Center( )
child: Empty(text: '暂无数据'), : ListView.separated(
) separatorBuilder: (context, index) => const Divider(
: ListView.separated( color: AppTheme.dividerColor,
separatorBuilder: (context, index) => const Divider( ),
color: AppTheme.dividerColor, itemCount: controller.list.length,
), itemBuilder: (_, index) => itemBuilder != null
itemCount: controller.list.length, ? itemBuilder!(_, index)
itemBuilder: (_, index) => itemBuilder != null : buildItem(index))));
? itemBuilder!(_, index)
: buildItem(index))));
} }
Widget buildItem(int index) { Widget buildItem(int index) {

View File

@ -0,0 +1,32 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class SkTag extends StatelessWidget {
final String text;
final Color? color;
const SkTag({super.key, required this.text, required this.color});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.height(10),
vertical: ScreenAdaper.height(4)),
decoration: BoxDecoration(
border: Border.all(
color: (color ?? AppTheme.primaryColorLight).withOpacity(0.1)),
color: (color ?? AppTheme.primaryColorLight).withOpacity(0.2),
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)),
),
child: Text(
text,
style: TextStyle(
color: color ?? AppTheme.primaryColorLight,
fontWeight: FontWeight.w600,
fontSize: ScreenAdaper.sp(30)),
),
);
}
}

View File

@ -6,7 +6,7 @@ class SkTextInput extends StatefulWidget {
final TextEditingController textController; final TextEditingController textController;
final Function(FocusNode)? onTap; final Function(FocusNode)? onTap;
final bool isRequired; final bool isRequired;
final String labelText; final String? labelText;
final String? hint; final String? hint;
final bool isTextArea; final bool isTextArea;
final bool isDense; final bool isDense;
@ -16,6 +16,10 @@ class SkTextInput extends StatefulWidget {
final EdgeInsetsGeometry? contentPadding; final EdgeInsetsGeometry? contentPadding;
final bool autoFocus; final bool autoFocus;
final ValueChanged<String>? onFieldSubmitted; final ValueChanged<String>? onFieldSubmitted;
final Icon? prefix;
final Widget? suffixIcon;
final InputBorder? border;
final FloatingLabelBehavior? floatingLabelBehavior;
const SkTextInput( const SkTextInput(
{super.key, {super.key,
required this.textController, required this.textController,
@ -24,8 +28,12 @@ class SkTextInput extends StatefulWidget {
this.onFieldSubmitted, this.onFieldSubmitted,
this.isRequired = false, this.isRequired = false,
this.onTapOutside, this.onTapOutside,
this.labelText = '', this.labelText,
this.prefix,
this.suffixIcon,
this.onChanged, this.onChanged,
this.border,
this.floatingLabelBehavior = FloatingLabelBehavior.always,
this.isTextArea = false, this.isTextArea = false,
this.autoFocus = false, this.autoFocus = false,
this.contentPadding, this.contentPadding,
@ -74,26 +82,33 @@ class _SkTextInputState extends State<SkTextInput> {
onFieldSubmitted: widget.onFieldSubmitted, onFieldSubmitted: widget.onFieldSubmitted,
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
validator: widget.validator, validator: widget.validator,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: widget.prefix,
suffixIcon: widget.suffixIcon,
errorStyle: const TextStyle(fontSize: 0, height: 0.01), errorStyle: const TextStyle(fontSize: 0, height: 0.01),
contentPadding: widget.contentPadding, contentPadding: widget.contentPadding,
isDense: widget.isDense, isDense: widget.isDense,
floatingLabelBehavior: FloatingLabelBehavior.always, border: widget.border,
label: Row( floatingLabelBehavior: widget.floatingLabelBehavior,
crossAxisAlignment: CrossAxisAlignment.center, label: widget.labelText != null
mainAxisSize: MainAxisSize.min, ? Row(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
if (widget.isRequired) mainAxisSize: MainAxisSize.min,
Text( children: [
"*", if (widget.isRequired)
style: TextStyle( Text(
color: Colors.red, fontSize: ScreenAdaper.height(30)), "*",
), style: TextStyle(
Text( color: Colors.red,
widget.labelText, fontSize: ScreenAdaper.height(30)),
style: TextStyle(fontSize: ScreenAdaper.height(30)), ),
), Text(
]), widget.labelText!,
style: TextStyle(fontSize: ScreenAdaper.height(30)),
),
])
: null,
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: AppTheme.primaryColorLight, width: 2), borderSide: BorderSide(color: AppTheme.primaryColorLight, width: 2),
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))), borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),

View File

@ -1,22 +1,39 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/util.dart'; 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;
final bool hideLeading; final bool hideLeading;
final PreferredSizeWidget? bottom;
final Color? backgroundColor;
final Color? iconAndTextColor;
const SkAppbar( const SkAppbar(
{super.key, required this.title, this.action, this.hideLeading = false}); {super.key,
required this.title,
this.action,
this.hideLeading = false,
this.backgroundColor,
this.iconAndTextColor,
this.bottom});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppBar( return AppBar(
titleTextStyle: TextStyle(
color: iconAndTextColor,
fontWeight: FontWeight.w600,
letterSpacing: ScreenAdaper.width(2)),
backgroundColor: backgroundColor,
bottom: bottom,
leading: hideLeading leading: hideLeading
? const SizedBox() ? const SizedBox()
: IconButton( : IconButton(
icon: Icon( icon: Icon(
Icons.arrow_back_ios, Icons.arrow_back_ios,
size: ScreenAdaper.height(40), size: ScreenAdaper.height(40),
color: iconAndTextColor,
), ),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);