feat: hr manage

This commit is contained in:
louis 2024-04-09 08:31:17 +08:00
parent 93a30355cd
commit cee31e6896
36 changed files with 1601 additions and 250 deletions

View File

@ -45,5 +45,21 @@
android:name="android.permission.READ_EXTERNAL_STORAGE" /> android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- Provide required visibility configuration for API level 30 and above -->
<queries>
<!-- If your app checks for SMS support -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="sms" />
</intent>
<!-- If your app checks for call support -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="tel" />
</intent>
<!-- If your application checks for inAppBrowserView launch mode support -->
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
</manifest> </manifest>

View File

@ -3,7 +3,7 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true />
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
@ -25,7 +25,7 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true />
<key>Launch screen interface file base name</key> <key>Launch screen interface file base name</key>
<string>$(LAUNCH_SCREEN_STORYBOARD)</string> <string>$(LAUNCH_SCREEN_STORYBOARD)</string>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
@ -35,13 +35,18 @@
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>App需要您的同意,才能访问相册</string> <string>App需要您的同意,才能访问相册</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true />
<key>LSApplicationQueriesSchemes</key>
<array>
<string>sms</string>
<string>tel</string>
</array>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen.storyboard</string> <string>LaunchScreen.storyboard</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
<string>Main</string> <string>Main</string>
<key>UIStatusBarHidden</key> <key>UIStatusBarHidden</key>
<false/> <false />
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
@ -56,6 +61,6 @@
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false />
</dict> </dict>
</plist> </plist>

View File

@ -22,13 +22,18 @@ class Api {
); );
} }
// //
static Future<Response> getUserInfo() { static Future<Response> getMyProfile() {
return DioService.dio.get( return DioService.dio.get(
Urls.userInfo, Urls.userInfo,
); );
} }
//
static Future<Response> getUserInfo(int userid) {
return DioService.dio.get('${Urls.userInfo}/$userid');
}
// //
static Future<Response<PaginationData>> getProjects(Map params) { static Future<Response<PaginationData>> getProjects(Map params) {
return DioService.dio.get<PaginationData>(Urls.projects, return DioService.dio.get<PaginationData>(Urls.projects,

View File

@ -26,7 +26,7 @@ class AppTheme {
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 appbarBgColor = AppTheme.primaryColor;
static const Color scaffoldBackgroundColor = Color(0xFFf5f8ff); static const Color scaffoldBackgroundColor = Color(0XFFe9f0fd);
static const Color inputFillColor = Color(0xFFf5f8ff); static const Color inputFillColor = Color(0xFFf5f8ff);
} }

View File

@ -5,7 +5,7 @@ class Urls {
static String deleteAccount = 'user/deleteAccount'; static String deleteAccount = 'user/deleteAccount';
static String saveUserInfo = 'user/saveUserInfo'; static String saveUserInfo = 'user/saveUserInfo';
static String sysUser = 'system/users'; static String sysUser = 'system/users';
static String userInfo = 'account/profile'; static String myProfile = 'account/profile';
static String projects = 'project'; static String projects = 'project';
static String products = 'product'; static String products = 'product';
static String inventoryInout = 'materials-in-out'; static String inventoryInout = 'materials-in-out';
@ -15,4 +15,5 @@ class Urls {
static String uploadAttachemnt = 'tools/upload'; static String uploadAttachemnt = 'tools/upload';
static String systemParamConfig = 'system/param-config'; static String systemParamConfig = 'system/param-config';
static String accountMenus = 'account/menus'; static String accountMenus = 'account/menus';
static String userInfo = 'system/users';
} }

View File

@ -1,4 +1,6 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sk_base_mobile/models/user_info.model.dart';
import 'package:sk_base_mobile/screens/hr_manage/components/employee_detail.dart';
import 'package:sk_base_mobile/screens/hr_manage/hr_manage.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';
@ -14,6 +16,7 @@ class RouteConfig {
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 const String hrManage = '/hr_manage';
static const String employeeDetail = '/employee_detail';
static final List<GetPage> getPages = [ static final List<GetPage> getPages = [
GetPage(name: login, page: () => LoginScreen()), GetPage(name: login, page: () => LoginScreen()),
@ -21,6 +24,7 @@ class RouteConfig {
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()) GetPage(name: hrManage, page: () => HrManagePage()),
GetPage(name: employeeDetail, page: () => EmployeeDetail())
]; ];
} }

View File

@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/core/sk_cascade_picker.dart';
import 'package:sk_base_mobile/widgets/gradient_button.dart';
class DeptPicker extends StatelessWidget {
DeptPicker({super.key});
final _cascadeController = Get.put(CascadeController());
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: AppTheme.nearlyWhite),
height: ScreenAdaper.height(400),
child: Column(
children: [
GradientButton(
buttonText: '确定',
onPressed: () {
if (_cascadeController.isCompleted()) {
// titles
List<String> selectedTitles =
_cascadeController.selectedTitles;
print("已选中的titles: $selectedTitles");
//
List<int> selectedIndexes =
_cascadeController.selectedIndexes;
print("已选中的序号:$selectedIndexes");
Item item = _cascadeController
.items[selectedIndexes[0]]
.children![selectedIndexes[1]]
.children![selectedIndexes[2]];
print("已选择item( ${item.name} )");
}
},
),
SizedBox(
height: 10,
),
Expanded(
child: CascadePicker(
initialPageData:
_cascadeController.items.map((e) => e.name!).toList(),
nextPageData: (pageCallback, currentPage, selectIndex) async {
print("当前选择: 第$currentPage页, 第$selectIndex项");
if (currentPage == 1) {
//
List<String>? nextPageData = _cascadeController
.items[selectIndex].children
?.map((e) => e.name!)
.toList();
if (nextPageData != null) pageCallback(nextPageData);
} else if (currentPage == 2) {
//
//
List<int> selectedIndexes =
_cascadeController.selectedIndexes;
// items中获取下一级页面的列表数据
List<String>? nextPageData = _cascadeController
.items[selectedIndexes[0]].children?[selectIndex].children
?.map((e) => e.name!)
.toList();
if (nextPageData != null) pageCallback(nextPageData);
}
},
controller: _cascadeController,
maxPageNum: 3,
tabTitleStyle: TextStyle(
fontSize: ScreenAdaper.height(26), color: Colors.black),
itemTitleStyle: TextStyle(
fontSize: ScreenAdaper.height(26), color: Colors.black),
selectedIcon:
Icon(Icons.check, color: AppTheme.primaryColorLight),
))
],
));
}
}
class Item {
String? name;
String? code;
String? fatherCode;
String? remark;
List<Item>? children;
}

View File

@ -0,0 +1,285 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.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/screens/hr_manage/components/dept_picker.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/util/snack_bar.util.dart';
import 'package:sk_base_mobile/widgets/core/sk_cascade_picker.dart';
import 'package:sk_base_mobile/widgets/core/sk_dialog_header.dart';
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
import 'package:sk_base_mobile/widgets/gradient_button.dart';
///
class EditUserInfo extends StatelessWidget {
final int userId;
final EditUserInfoController controller;
EditUserInfo({super.key, required this.userId})
: controller = Get.put(EditUserInfoController(userId));
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: buildBody(),
);
}
Widget buildBody() {
return Column(
children: [
const SkDialogHeader(title: '编辑员工信息'),
Expanded(
child: SingleChildScrollView(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.height(15),
vertical: ScreenAdaper.height(15)),
child: Column(children: [
buildAvatar(),
SizedBox(
height: ScreenAdaper.height(defaultPadding),
),
SkTextInput(
isDense: true,
textController: controller.nameEditController,
labelText: '姓名',
),
SizedBox(
height: ScreenAdaper.height(defaultPadding),
),
SkTextInput(
isDense: true,
keyboardType: TextInputType.none,
textController: TextEditingController(),
labelText: '所属部门',
onTap: (_) async {
Get.bottomSheet(DeptPicker())
.then((value) => Get.delete<CascadeController>());
},
),
SizedBox(
height: ScreenAdaper.height(defaultPadding),
),
SkTextInput(
isDense: true,
keyboardType: TextInputType.none,
textController: TextEditingController(),
labelText: '角色',
onTap: (_) async {
Get.bottomSheet(Container(
height: ScreenAdaper.height(400),
decoration: BoxDecoration(color: AppTheme.nearlyWhite),
child: Column(children: [
Container(
padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(20),
horizontal: ScreenAdaper.width(20)),
child: Row(
children: [
Text(
'取消',
style: TextStyle(
fontSize: ScreenAdaper.height(30)),
),
Spacer(),
Text(
'确定',
style: TextStyle(
fontSize: ScreenAdaper.height(30),
color: AppTheme.primaryColor),
)
],
),
),
Expanded(
child: SingleChildScrollView(
child: ListBody(
children: <String>[
'角色1',
'角色2',
'角色3',
'角色4',
'角色5',
'角色6',
'角色7',
'角色8',
'角色9'
].map((String text) {
return Obx(
() => CheckboxListTile(
contentPadding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(0),
horizontal: ScreenAdaper.width(20)),
title: Text(text),
value:
controller.selectedDepts.contains(text),
onChanged: (bool? value) {
if (value == true) {
controller.selectedDepts.add(text);
} else {
controller.selectedDepts.remove(text);
}
},
),
);
}).toList(),
),
))
]),
));
}),
SizedBox(
height: ScreenAdaper.height(defaultPadding),
),
SkTextInput(
isDense: true,
textController: TextEditingController(),
labelText: '登录用户名',
),
SizedBox(
height: ScreenAdaper.height(defaultPadding),
),
SkTextInput(
isDense: true,
textController: TextEditingController(),
labelText: '手机号',
)
]),
),
)),
const GradientButton(
borderRadius: BorderRadius.zero,
buttonText: '提交',
)
// Container(
// decoration: BoxDecoration(color: AppTheme.primaryColor),
// height: 100,
// )
],
);
}
Widget buildAvatar() {
return Column(
children: [
SizedBox(
height: ScreenAdaper.height(10),
),
buildImageUploader(),
],
);
}
Widget builderImagePreview(String path, String type) {
return Stack(
children: [
Container(
margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
width: ScreenAdaper.width(180),
height: ScreenAdaper.width(180),
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: () {
if (type == 'agent') {
// controller.uploadAgentImgFilesPath.remove(path);
} else {
// controller.uploadProductImgFilesPath.remove(path);
}
},
child: Icon(
Icons.close,
shadows: const [
Shadow(
color: Colors.black,
offset: Offset(1, 1),
blurRadius: 3,
),
Shadow(
color: Colors.black,
offset: Offset(-1, -1),
blurRadius: 3,
),
],
size: ScreenAdaper.height(40),
color: AppTheme.nearlyWhite,
),
))
],
);
}
//
Widget buildImageUploader() {
return GestureDetector(
onTap: () {
// controller.photoPicker(type);
},
child: Container(
margin: EdgeInsets.symmetric(horizontal: ScreenAdaper.width(5)),
width: ScreenAdaper.width(180),
height: ScreenAdaper.width(180),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
border: Border.all(width: 1.0, color: AppTheme.dividerColor),
// color: AppTheme.primaryColor,
),
child: Center(
child: Icon(
Icons.add_a_photo_rounded,
size: ScreenAdaper.height(60),
color: AppTheme.primaryColor,
))));
}
}
class EditUserInfoController extends GetxController {
int userId;
EditUserInfoController(this.userId);
final nameEditController = TextEditingController();
final userInfo = Rxn<UserInfoModel>();
RxList selectedDepts = RxList([]);
@override
onReady() {
getUserInfo();
super.onReady();
}
Future<void> getUserInfo() async {
try {
final response = await Api.getUserInfo(userId);
if (response.data != null) {
userInfo.value = UserInfoModel.fromJson(response.data);
nameEditController.text = userInfo.value?.nickname ?? '';
}
} catch (e) {
SnackBarUtil().error('$e');
} finally {}
}
}

View File

@ -0,0 +1,214 @@
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/config.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/screens/inventory_inout/components/responsive.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/core/sk_tag.dart';
import 'package:sk_base_mobile/widgets/fade_in_cache_image.dart';
import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
class EmployeeDetail extends StatelessWidget {
final _controller = Get.put(EmployeeDetailController());
EmployeeDetail({super.key});
final userInfo = Get.arguments as UserInfoModel;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const SkAppbar(
backgroundColor: AppTheme.white,
iconAndTextColor: AppTheme.black,
title: '员工详情',
),
body: buildBody(),
);
}
Widget buildBody() {
return Column(
children: [
buildTopInfo(),
Expanded(child: buildContent()),
],
);
}
Widget buildTopInfo() {
return Container(
padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(defaultPadding),
horizontal: ScreenAdaper.width(defaultPadding * 2)),
color: AppTheme.white,
child: Row(
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(30)),
child: FadeInCacheImage(
height: ScreenAdaper.height(80),
width: ScreenAdaper.height(80),
url: '${GloablConfig.OSS_URL}${userInfo.avatar}'),
),
SizedBox(
width: ScreenAdaper.width(defaultPadding),
),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${userInfo.nickname}',
style: TextStyle(
fontSize: ScreenAdaper.height(30),
fontWeight: FontWeight.w600)),
SizedBox(
height: ScreenAdaper.height(5),
), // role
Text('${userInfo.dept?.name}',
style: TextStyle(color: AppTheme.grey)),
SizedBox(
height: ScreenAdaper.height(5),
),
Wrap(
spacing: ScreenAdaper.height(5),
runSpacing: ScreenAdaper.height(5),
children: [
...userInfo.roles.map((e) => SkTag(
text: '${e.name}', color: AppTheme.primaryColorLight))
],
),
],
)),
],
),
);
}
Widget buildContent() {
return Container(
margin: EdgeInsets.all(ScreenAdaper.height(defaultPadding)),
decoration: const BoxDecoration(color: AppTheme.white),
child: DefaultTabController(
length: 4,
initialIndex: _controller.selectedTabIndex.value,
child: Column(
children: [
_buildTabBar(),
Expanded(
child: _buildTabView(),
)
],
)),
);
}
Widget _buildTabBar() {
return TabBar(
onTap: (index) {
_controller.selectedTabIndex.value = index;
},
labelStyle: TextStyle(
fontSize: ScreenAdaper.height(25), fontWeight: FontWeight.w600),
labelPadding: EdgeInsets.zero,
labelColor: AppTheme.primaryColorLight,
// unselectedLabelColor: AppTheme.grey,
indicator: BoxDecoration(
border: Border(
bottom: BorderSide(
width: ScreenAdaper.height(5),
color: AppTheme.primaryColorLight,
)),
),
tabs: const [
Tab(
text: '基本信息',
),
Tab(
text: '考勤记录',
),
Tab(
text: '工作安排',
),
Tab(
text: '相关文件',
),
],
);
}
Widget _buildTabView() {
return TabBarView(
children: [
Responsive(
tablet: buildBaseInfo(
columnNum: 3,
),
largeTablet: buildBaseInfo(
columnNum: 4,
),
mobile: buildBaseInfo(
columnNum: 2,
)),
Container(
child: Text('考勤记录'),
),
Container(
child: Text('请假记录'),
),
Container(
child: Text('加班记录'),
),
],
);
}
//
Widget buildBaseInfo({
required int columnNum,
}) {
return Container(
padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(defaultPadding),
horizontal: ScreenAdaper.height(defaultPadding)),
child: MasonryGridView.count(
crossAxisCount: columnNum,
itemCount: 7,
mainAxisSpacing: ScreenAdaper.height(50),
itemBuilder: (context, index) {
return buildBaseInfoItem(label: '姓名', value: '张三');
},
));
}
Widget buildBaseInfoItem({String? label, String? value}) {
return Container(
// decoration: BoxDecoration(color: Colors.red),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$label',
style: TextStyle(
fontSize: ScreenAdaper.height(26),
color: AppTheme.grey,
fontWeight: FontWeight.w600),
),
SizedBox(
height: ScreenAdaper.height(10),
),
Text(
'$value',
style: TextStyle(fontSize: ScreenAdaper.height(26)),
)
],
),
);
}
}
class EmployeeDetailController extends GetxController {
final selectedTabIndex = 0.obs;
}

View File

@ -3,16 +3,26 @@ import 'package:get/get.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:sk_base_mobile/apis/index.dart'; import 'package:sk_base_mobile/apis/index.dart';
import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/config.dart';
import 'package:sk_base_mobile/constants/bg_color.dart'; import 'package:sk_base_mobile/constants/bg_color.dart';
import 'package:sk_base_mobile/constants/constants.dart';
import 'package:sk_base_mobile/models/user_info.model.dart'; import 'package:sk_base_mobile/models/user_info.model.dart';
import 'package:sk_base_mobile/screens/hr_manage/components/edit_userinfo.dart';
import 'package:sk_base_mobile/util/common.util.dart';
import 'package:sk_base_mobile/util/date.util.dart'; import 'package:sk_base_mobile/util/date.util.dart';
import 'package:sk_base_mobile/util/debouncer.dart'; import 'package:sk_base_mobile/util/debouncer.dart';
import 'package:sk_base_mobile/util/device.util.dart';
import 'package:sk_base_mobile/util/media_util.dart';
import 'package:sk_base_mobile/util/modal.util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/util/snack_bar.util.dart';
import 'package:sk_base_mobile/widgets/core/sk_ink.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_tag.dart';
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart'; import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
import 'package:sk_base_mobile/widgets/empty.dart'; import 'package:sk_base_mobile/widgets/empty.dart';
import 'package:sk_base_mobile/widgets/sk_appbar.dart'; import 'package:sk_base_mobile/widgets/fade_in_cache_image.dart';
import 'package:sk_base_mobile/widgets/loading_indicator.dart';
import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
class HrManagePage extends StatelessWidget { class HrManagePage extends StatelessWidget {
final controller = Get.put(HrManageController()); final controller = Get.put(HrManageController());
@ -20,14 +30,21 @@ class HrManagePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// return Column(
// children: [
// Expanded(
// child: Material(
// child: EditUserInfo(),
// ))
// ],
// );
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
// //
FocusScope.of(context).requestFocus(FocusNode()); FocusScope.of(context).requestFocus(FocusNode());
}, },
child: Scaffold( child: Scaffold(
backgroundColor: Color(0xFFe9f0fd), appBar: const SkAppbar(
appBar: SkAppbar(
backgroundColor: AppTheme.nearlyWhite, backgroundColor: AppTheme.nearlyWhite,
iconAndTextColor: AppTheme.black, iconAndTextColor: AppTheme.black,
title: '人事管理', title: '人事管理',
@ -44,9 +61,11 @@ class HrManagePage extends StatelessWidget {
child: Container( child: Container(
margin: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(
horizontal: ScreenAdaper.height(defaultPadding), horizontal: ScreenAdaper.height(defaultPadding),
vertical: ScreenAdaper.height(defaultPadding)), ),
child: Obx( child: Obx(
() => SmartRefresher( () => controller.loading.value
? const LoadingIndicator(common: true)
: SmartRefresher(
enablePullDown: true, enablePullDown: true,
enablePullUp: true, enablePullUp: true,
controller: controller.refreshController, controller: controller.refreshController,
@ -54,9 +73,12 @@ class HrManagePage extends StatelessWidget {
onRefresh: controller.onRefresh, onRefresh: controller.onRefresh,
child: controller.list.isEmpty child: controller.list.isEmpty
? const Center( ? const Center(
child: Empty(text: '暂无数据'), child: Empty(text: '暂无员工'),
) )
: ListView.builder( : ListView.builder(
padding: EdgeInsets.symmetric(
vertical:
ScreenAdaper.height(defaultPadding)),
itemBuilder: (context, index) { itemBuilder: (context, index) {
return buildUserCard(index); return buildUserCard(index);
}, },
@ -75,11 +97,8 @@ class HrManagePage extends StatelessWidget {
}, delayTime: 500); }, delayTime: 500);
return Container( return Container(
color: AppTheme.nearlyWhite, color: AppTheme.nearlyWhite,
padding: EdgeInsets.only( padding: EdgeInsets.all(ScreenAdaper.width(20)),
right: ScreenAdaper.width(20), child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
left: ScreenAdaper.width(20),
bottom: ScreenAdaper.height(20)),
child: Row(children: [
Expanded( Expanded(
child: SizedBox( child: SizedBox(
height: ScreenAdaper.height(70), height: ScreenAdaper.height(70),
@ -132,25 +151,29 @@ class HrManagePage extends StatelessWidget {
} }
Widget buildUserCard(int index) { Widget buildUserCard(int index) {
return Container( return SkInk(
onTap: () {
Get.toNamed(RouteConfig.employeeDetail,
arguments: controller.list[index]);
},
margin: EdgeInsets.only(bottom: ScreenAdaper.height(defaultPadding)),
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)),
child: Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.height(defaultPadding), horizontal: ScreenAdaper.height(defaultPadding),
vertical: 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( child: Column(
children: [ children: [
Row( Row(
children: [ children: [
// //
ClipRRect( ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)), borderRadius: const BorderRadius.all(Radius.circular(30)),
child: Image( child: FadeInCacheImage(
height: ScreenAdaper.height(80), height: ScreenAdaper.height(80),
image: NetworkImage( width: ScreenAdaper.height(80),
'https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=1743369777')), url:
MediaUtil.getMediaUrl(controller.list[index].avatar)),
), ),
SizedBox( SizedBox(
width: ScreenAdaper.height(defaultPadding), width: ScreenAdaper.height(defaultPadding),
@ -179,7 +202,8 @@ class HrManagePage extends StatelessWidget {
runSpacing: ScreenAdaper.height(5), runSpacing: ScreenAdaper.height(5),
children: [ children: [
...controller.list[index].roles.map((e) => SkTag( ...controller.list[index].roles.map((e) => SkTag(
text: '${e.name}', color: AppTheme.primaryColorLight)) text: '${e.name}',
color: AppTheme.primaryColorLight))
], ],
)), )),
], ],
@ -188,7 +212,13 @@ class HrManagePage extends StatelessWidget {
/// action /// action
/// ///
buildActionButton( buildActionButton(
onTap: () {}, onTap: () {
if (controller.list[index].phone == null) {
SnackBarUtil().info('该员工没有录入手机号');
return;
}
DeviceUtil.callPhone(controller.list[index].phone!);
},
icon: Icons.phone, icon: Icons.phone,
), ),
SizedBox( SizedBox(
@ -197,7 +227,13 @@ class HrManagePage extends StatelessWidget {
/// ///
buildActionButton( buildActionButton(
onTap: () {}, onTap: () {
if (controller.list[index].email == null) {
SnackBarUtil().info('该员工没有录入邮箱');
return;
}
DeviceUtil.sendEmail(controller.list[index].email!);
},
icon: Icons.email_outlined, icon: Icons.email_outlined,
), ),
SizedBox( SizedBox(
@ -206,7 +242,15 @@ class HrManagePage extends StatelessWidget {
/// ///
buildActionButton( buildActionButton(
onTap: () {}, onTap: () {
ModalUtil.showGeneralDialog(
content: EditUserInfo(
userId: controller.list[index].id!,
)).then((value) => Get.delete<EditUserInfoController>());
// Get.toNamed(RouteConfig.employeeDetail,
// arguments: controller.list[index]);
},
icon: Icons.edit, icon: Icons.edit,
) )
], ],
@ -225,6 +269,7 @@ class HrManagePage extends StatelessWidget {
) )
], ],
), ),
),
); );
} }
@ -233,14 +278,14 @@ class HrManagePage extends StatelessWidget {
required IconData icon, required IconData icon,
}) { }) {
return SkInk( return SkInk(
onTap: () {}, onTap: onTap,
border: Border.all(color: AppTheme.grey.withOpacity(0.8)), border: Border.all(color: AppTheme.grey.withOpacity(0.8)),
borderRadius: BorderRadius.circular(ScreenAdaper.sp(30)), borderRadius: BorderRadius.circular(ScreenAdaper.sp(40)),
child: Container( child: Container(
padding: EdgeInsets.all(ScreenAdaper.height(5)), padding: EdgeInsets.all(ScreenAdaper.height(5)),
child: Icon( child: Icon(
icon, icon,
size: ScreenAdaper.height(35), size: ScreenAdaper.height(40),
), ),
), ),
); );
@ -251,10 +296,27 @@ class HrManageController extends GetxController {
RxList<UserInfoModel> list = RxList([]); RxList<UserInfoModel> list = RxList([]);
RxString searchKey = ''.obs; RxString searchKey = ''.obs;
final searchBarTextConroller = TextEditingController(); final searchBarTextConroller = TextEditingController();
RefreshController refreshController = RefreshController(initialRefresh: true); RefreshController refreshController =
RefreshController(initialRefresh: false);
int page = 1; int page = 1;
int limit = 15; int limit = 15;
int total = 0; int total = 0;
RxBool loading = false.obs;
@override
onReady() {
super.onReady();
initData();
}
initData() async {
loading.value = true;
try {
await getData(isRefresh: true);
} finally {
loading.value = false;
}
}
Future<List<UserInfoModel>> getData({bool isRefresh = false}) async { Future<List<UserInfoModel>> getData({bool isRefresh = false}) async {
if (isRefresh == true) { if (isRefresh == true) {
page = 1; page = 1;

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/components/inventory_search.dart'; import 'package:sk_base_mobile/screens/new_inventory_inout/components/inventory_search.dart';
import 'package:sk_base_mobile/widgets/sk_appbar.dart'; import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
class InventoryPage extends StatelessWidget { class InventoryPage extends StatelessWidget {
final bool isPage; final bool isPage;

View File

@ -11,6 +11,7 @@ import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/fade_in_cache_image.dart'; import 'package:sk_base_mobile/widgets/fade_in_cache_image.dart';
import 'package:sk_base_mobile/widgets/image_preview.dart'; import 'package:sk_base_mobile/widgets/image_preview.dart';
import 'package:sk_base_mobile/widgets/loading_indicator.dart'; import 'package:sk_base_mobile/widgets/loading_indicator.dart';
import 'package:sk_base_mobile/widgets/core/sk_dialog_header.dart';
class InventoryInoutInfo extends StatelessWidget { class InventoryInoutInfo extends StatelessWidget {
final int inventoryInoutId; final int inventoryInoutId;
@ -22,23 +23,7 @@ class InventoryInoutInfo extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
child: Column(children: [ child: Column(children: [
Container( const SkDialogHeader(title: '出入库详情'),
padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(10),
vertical: ScreenAdaper.height(15)),
decoration: const BoxDecoration(color: AppTheme.primaryColor),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'出入库详情',
style: TextStyle(
fontSize: ScreenAdaper.height(25),
color: AppTheme.nearlyWhite),
),
],
),
),
Expanded( Expanded(
child: Obx(() => controller.inventoryInoutInfo.value == null child: Obx(() => controller.inventoryInoutInfo.value == null
? const Center(child: LoadingIndicator(common: true)) ? const Center(child: LoadingIndicator(common: true))

View File

@ -15,7 +15,7 @@ class MineController extends GetxController {
@override @override
void onInit() { void onInit() {
// AuthStore.to.getUserInfo(); // AuthStore.to.getMyProfile();
super.onInit(); super.onInit();
} }

View File

@ -19,7 +19,7 @@ import 'package:sk_base_mobile/widgets/core/sk_date_picker.dart';
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart'; import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
import 'package:sk_base_mobile/widgets/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/widgets/sk_appbar.dart'; import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
class NewInventoryInout extends StatelessWidget { class NewInventoryInout extends StatelessWidget {
final NewInventoryInoutController controller; final NewInventoryInoutController controller;

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/gradient_button.dart'; import 'package:sk_base_mobile/widgets/gradient_button.dart';
import 'package:sk_base_mobile/widgets/sk_appbar.dart'; import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
class SaleQuotationEndDrawer extends StatelessWidget { class SaleQuotationEndDrawer extends StatelessWidget {
const SaleQuotationEndDrawer({super.key}); const SaleQuotationEndDrawer({super.key});

View File

@ -1,5 +1,3 @@
import 'dart:ffi';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_slidable/flutter_slidable.dart';
@ -14,10 +12,9 @@ import 'package:sk_base_mobile/util/snack_bar.util.dart';
import 'package:sk_base_mobile/widgets/core/sk_number_input.dart'; import 'package:sk_base_mobile/widgets/core/sk_number_input.dart';
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart'; import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
import 'package:sk_base_mobile/widgets/empty.dart'; import 'package:sk_base_mobile/widgets/empty.dart';
import 'package:sk_base_mobile/widgets/sk_appbar.dart'; import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
import 'package:flutter_sticky_header/flutter_sticky_header.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart';
import 'package:math_expressions/math_expressions.dart'; import 'package:math_expressions/math_expressions.dart';
import 'dart:math' as math;
class SaleQuotationPage extends StatelessWidget { class SaleQuotationPage extends StatelessWidget {
SaleQuotationPage({super.key}); SaleQuotationPage({super.key});

View File

@ -7,7 +7,7 @@ import 'package:sk_base_mobile/constants/router.dart';
import 'package:sk_base_mobile/screens/workbench/workbench_controller.dart'; import 'package:sk_base_mobile/screens/workbench/workbench_controller.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/util/snack_bar.util.dart'; import 'package:sk_base_mobile/util/snack_bar.util.dart';
import 'package:sk_base_mobile/widgets/sk_appbar.dart'; import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
class WorkBenchPage extends StatelessWidget { class WorkBenchPage extends StatelessWidget {
WorkBenchPage({super.key}); WorkBenchPage({super.key});

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sk_base_mobile/models/workbench.model.dart'; import 'package:sk_base_mobile/models/workbench.model.dart';

View File

@ -26,7 +26,7 @@ class AuthStore extends GetxService {
if (token != null) { if (token != null) {
if (preUserInfo != null) { if (preUserInfo != null) {
await getUserInfo(); await getMyProfile();
LoggerUtil().info('[Store-Auth] userId: ${userInfo.value.id}'); LoggerUtil().info('[Store-Auth] userId: ${userInfo.value.id}');
} }
} }
@ -87,7 +87,7 @@ class AuthStore extends GetxService {
await StorageService.to await StorageService.to
.setString(CacheKeys.token, auth.token!, isWithUser: false); .setString(CacheKeys.token, auth.token!, isWithUser: false);
} }
await getUserInfo(); await getMyProfile();
await getCommonInfo(); await getCommonInfo();
Get.offNamed(RouteConfig.home); Get.offNamed(RouteConfig.home);
} }
@ -105,9 +105,9 @@ class AuthStore extends GetxService {
.setString(CacheKeys.userInfo, jsonEncode(newInfo), isWithUser: false); .setString(CacheKeys.userInfo, jsonEncode(newInfo), isWithUser: false);
} }
Future<void> getUserInfo() async { Future<void> getMyProfile() async {
try { try {
final response = await Api.getUserInfo(); final response = await Api.getMyProfile();
if (response.data != null) { if (response.data != null) {
UserInfoModel userInfo = UserInfoModel.fromJson(response.data); UserInfoModel userInfo = UserInfoModel.fromJson(response.data);
await updateUserInfoState(userInfo); await updateUserInfoState(userInfo);
@ -122,7 +122,7 @@ class AuthStore extends GetxService {
try { try {
final res = await Api.saveUserInfo(data); final res = await Api.saveUserInfo(data);
if (res.data != null) { if (res.data != null) {
await getUserInfo(); await getMyProfile();
SnackBarUtil().success('Save successfully.'); SnackBarUtil().success('Save successfully.');
} }
} catch (e) { } catch (e) {

View File

@ -1,7 +1,30 @@
import 'dart:io'; import 'dart:io';
import 'package:package_info/package_info.dart'; import 'package:package_info/package_info.dart';
import 'package:url_launcher/url_launcher.dart';
class DeviceUtil { class DeviceUtil {
///
static void sendEmail(String email) {
if (email.isNotEmpty) {
String url = 'mailto:$email';
launchUrl(Uri.parse(url));
}
}
///
static void callPhone(String phone) {
if (phone.isNotEmpty) {
String url = 'tel:$phone';
if (Platform.isIOS) {
url = 'tel://$phone';
}
if (Platform.isAndroid) {
url = 'tel:$phone';
}
launchUrl(Uri.parse(url));
}
}
/// ///
static Future<String> getAppVersion() async { static Future<String> getAppVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform(); PackageInfo packageInfo = await PackageInfo.fromPlatform();

View File

@ -5,10 +5,19 @@ import 'package:dio/dio.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:sk_base_mobile/apis/index.dart'; import 'package:sk_base_mobile/apis/index.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:sk_base_mobile/config.dart';
import 'package:sk_base_mobile/models/upload_result.model.dart'; import 'package:sk_base_mobile/models/upload_result.model.dart';
import 'package:sk_base_mobile/services/service.dart'; import 'package:sk_base_mobile/services/service.dart';
class MediaUtil { class MediaUtil {
static String? getMediaUrl(String? url) {
if ((url ?? '').isEmpty) {
return null;
} else {
return '${GloablConfig.OSS_URL}$url';
}
}
// //
Future<XFile?> getImageFromCamera( Future<XFile?> getImageFromCamera(
{double? maxWidth, bool isFront = false}) async { {double? maxWidth, bool isFront = false}) async {
@ -52,6 +61,15 @@ class MediaUtil {
return null; return null;
} }
//
Future saveImageToPhotoLib({required String imgUrl, String? filename}) async {
var response = await DioService.dio
.get(imgUrl, options: Options(responseType: ResponseType.bytes));
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(response.data),
quality: 60);
return result;
}
// // // //
// Future<String> uploadMediaWithProgress( // Future<String> uploadMediaWithProgress(
// File imgfile, Function onProgress) async { // File imgfile, Function onProgress) async {
@ -76,16 +94,6 @@ class MediaUtil {
// } // }
// } // }
//
Future saveImageToPhotoLib({required String imgUrl, String? filename}) async {
var response = await DioService.dio
.get(imgUrl, options: Options(responseType: ResponseType.bytes));
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(response.data),
quality: 60);
return result;
}
// /// // ///
// Future<XFile?> getVideoFromGallery() async { // Future<XFile?> getVideoFromGallery() async {
// if (AppInfoModel().isCameraing) { // if (AppInfoModel().isCameraing) {

View File

@ -167,7 +167,7 @@ class ModalUtil {
double? height, double? height,
Offset? offset}) { Offset? offset}) {
return Get.generalDialog( return Get.generalDialog(
barrierLabel: "productPicker", barrierLabel: "generalDialog",
barrierDismissible: true, barrierDismissible: true,
transitionDuration: const Duration(milliseconds: 400), transitionDuration: const Duration(milliseconds: 400),
pageBuilder: (_, __, ___) { pageBuilder: (_, __, ___) {

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: 1), duration: const Duration(seconds: 2),
dismissDirection: DismissDirection.horizontal, dismissDirection: DismissDirection.horizontal,
forwardAnimationCurve: Curves.fastLinearToSlowEaseIn, forwardAnimationCurve: Curves.fastLinearToSlowEaseIn,
reverseAnimationCurve: Curves.linearToEaseOut); reverseAnimationCurve: Curves.linearToEaseOut);

View File

@ -1,5 +1,4 @@
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 {

View File

@ -0,0 +1,468 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/screens/hr_manage/components/dept_picker.dart';
///
/// 使:
/// ```dart
/// CascadePicker的page是ListView
///
///
/// final _cascadeController = CascadeController();
///
/// initialPageData:
/// nextPageData:
/// - pageCallback: CascadePicker
/// - currentPage:
/// - selectIndex:
/// controller:
/// maxPageNum:
/// selectedIcon: flutter package不能放本地资源文件images文件夹下面
///
/// Expand(
/// child: CascadePicker(
/// initialPageData: ['a', 'b', 'c', 'd'],
/// nextPageData: (pageCallback, currentPage, selectIndex) async {
/// pageCallback(['one', 'two', 'three'])
/// },
/// controller: _cascadeController,
/// maxPageNum: 4,
/// selectedIcon: Image.asset("images/ic_select_mark.png", width: 10, height: 10, color: Colors.redAccent,),
/// )
///
/// InkBox(
/// child: Container(...)
/// onTap: () {
/// ///
/// if (_cascadeController.isCompleted()) {
/// List<String> selectedTitles = _cascadeController.selectedTitles;
/// List<int> selectedIndexes = _cascadeController.selectedIndexes;
/// }
/// }
/// )
/// ```
/// pageData:
/// currentPage: ,
/// selectIndex:
typedef void NextPageCallback(
Function(List<String>) pageData, int currentPage, int selectIndex);
class CascadePicker extends StatefulWidget {
final List<String> initialPageData;
final NextPageCallback nextPageData;
final int maxPageNum;
final CascadeController controller;
final Color tabColor;
final double tabHeight;
final TextStyle tabTitleStyle;
final double itemHeight;
final TextStyle itemTitleStyle;
final Color itemColor;
final Color activeColor;
final Widget? selectedIcon;
CascadePicker(
{required this.initialPageData,
required this.nextPageData,
this.maxPageNum = 3,
required this.controller,
this.tabHeight = 40,
this.activeColor = AppTheme.primaryColor,
this.tabColor = Colors.white,
this.tabTitleStyle = const TextStyle(color: Colors.black, fontSize: 14),
this.itemHeight = 40,
this.itemColor = Colors.white,
this.itemTitleStyle = const TextStyle(color: Colors.black, fontSize: 14),
this.selectedIcon});
@override
_CascadePickerState createState() => _CascadePickerState(this.controller);
}
class _CascadePickerState extends State<CascadePicker>
with SingleTickerProviderStateMixin {
static String _newTabName = "请选择";
final CascadeController _cascadeController;
_CascadePickerState(this._cascadeController) {
_cascadeController._setState(this);
}
late final AnimationController _controller;
late final CurvedAnimation _curvedAnimation;
Animation? _sliderAnimation;
final _sliderFixMargin = ValueNotifier(0.0);
double _sliderWidth = 20;
PageController _pageController = PageController(initialPage: 0);
GlobalKey _sliderKey = GlobalKey();
List<GlobalKey> _tabKeys = [];
///
List<List<String>> _pagesData = [];
/// title集合
List<String> _selectedTabs = [_newTabName];
/// item index集合
List<int> _selectedIndexes = [-1];
/// "请选择"tab宽度tab时用到
double _animTabWidth = 0;
/// tab添加事件记录"请选择"tab初始化状态
bool _isAddTabEvent = false;
/// tab移动未开始'请选择'tab时隐藏文本tab在终点位置
bool _isAnimateTextHide = false;
/// _moveSlider重复调用
bool _isClickAndMoveTab = false;
///
int _currentSelectPage = 0;
_addTab(int page, int atIndex, String currentPageItem) {
_loadNextPageData(page, atIndex, currentPageItem);
}
_loadNextPageData(int page, int atIndex, String currentPageItem,
{bool isUpdatePage = false}) {
widget.nextPageData((data) {
final nextPageDataIsEmpty = data.isEmpty;
if (!nextPageDataIsEmpty) {
///
setState(() {
if (isUpdatePage) {
///
_pagesData[page] = data;
_selectedTabs[page] = _newTabName;
_selectedIndexes[page] = -1;
/// tab数据
_pagesData.removeRange(page + 1, _pagesData.length);
_selectedIndexes.removeRange(page + 1, _selectedIndexes.length);
_selectedTabs.removeRange(page + 1, _selectedTabs.length);
} else {
///
_isAnimateTextHide = true;
_isAddTabEvent = true;
_pagesData.add(data);
_selectedTabs.add(_newTabName);
_selectedIndexes.add(-1);
}
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_moveSlider(page, isAdd: true);
});
});
} else {
///
final currentPage = page - 1;
setState(() {
_selectedTabs[currentPage] = currentPageItem;
_selectedIndexes[currentPage] = atIndex;
/// tab数据
_pagesData.removeRange(page, _pagesData.length);
_selectedIndexes.removeRange(page, _selectedIndexes.length);
_selectedTabs.removeRange(page, _selectedTabs.length);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
//
_moveSlider(currentPage);
});
});
}
}, page, atIndex);
}
_moveSlider(int page, {bool movePage = true, bool isAdd = false}) {
if (movePage && _currentSelectPage != page) {
/// tab标签
/// _isClickAndMoveTab设为truePageView
/// _moveSlider重复调用
_isClickAndMoveTab = true;
}
_isAddTabEvent = isAdd;
_currentSelectPage = page;
if (_controller.isAnimating) {
_controller.stop();
}
RenderBox slider =
_sliderKey.currentContext?.findRenderObject() as RenderBox;
Offset sliderPosition = slider.localToGlobal(Offset.zero);
RenderBox currentTabBox =
_tabKeys[page].currentContext?.findRenderObject() as RenderBox;
Offset currentTabPosition = currentTabBox.localToGlobal(Offset.zero);
_animTabWidth = currentTabBox.size.width;
final begin = sliderPosition.dx - _sliderFixMargin.value;
final end = currentTabPosition.dx +
(currentTabBox.size.width - _sliderWidth) / 2 -
_sliderFixMargin.value;
_sliderAnimation =
Tween<double>(begin: begin, end: end).animate(_curvedAnimation);
_controller.value = 0;
_controller.forward();
if (movePage) {
_pageController.animateToPage(page,
curve: Curves.linear, duration: Duration(milliseconds: 500));
}
}
/// tab渲染完成才开始动画moveSlider
Widget _animateTab({required Widget tab}) {
return Transform.translate(
offset: Offset(
Tween<double>(begin: _isAddTabEvent ? -_animTabWidth : 0, end: 0)
.evaluate(_curvedAnimation),
0),
child: Opacity(
///
opacity: _isAnimateTextHide ? 0 : 1,
child: tab),
);
}
List<Widget> _tabWidgets() {
List<Widget> widgets = [];
_tabKeys.clear();
for (int i = 0; i < _pagesData.length; i++) {
GlobalKey key = GlobalKey();
_tabKeys.add(key);
final tab = GestureDetector(
child: Container(
key: key,
height: widget.tabHeight,
color: widget.tabColor,
alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 15),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth:
MediaQuery.of(context).size.width / _pagesData.length - 10),
child: Text(
_selectedTabs[i],
style: _currentSelectPage == i
? widget.tabTitleStyle.copyWith(color: widget.activeColor)
: widget.tabTitleStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
onTap: () {
_moveSlider(i);
},
);
if (i == _pagesData.length - 1 && _selectedTabs[i] == _newTabName) {
widgets.add(_animateTab(tab: tab));
_isAnimateTextHide = false;
} else {
widgets.add(tab);
}
}
return widgets;
}
///
Widget _pageItemWidget(int index, int page, String item) {
return GestureDetector(
child: Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 15),
height: widget.itemHeight,
color: widget.itemColor,
child: Row(
children: [
item == _selectedTabs[page]
? Padding(
padding: const EdgeInsets.all(5.0),
child: widget.selectedIcon == null
? Icon(Icons.chevron_right,
size: 15, color: widget.activeColor)
: widget.selectedIcon,
)
: SizedBox(),
Text("$item",
style: item == _selectedTabs[page]
? widget.itemTitleStyle.copyWith(color: widget.activeColor)
: widget.itemTitleStyle),
],
),
),
onTap: () {
if (page == widget.maxPageNum - 1) {
///
setState(() {
_selectedTabs[page] = item;
_selectedIndexes[page] = index;
///
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_moveSlider(page);
});
});
} else if (_tabKeys.length >= widget.maxPageNum ||
page < _tabKeys.length - 1) {
if (index == _selectedIndexes[page]) {
/// item
_moveSlider(page + 1);
} else {
/// itemtab renderBox
setState(() {
_selectedTabs[page] = item;
_selectedIndexes[page] = index;
// _selectedIndexes.removeRange(page + 1, _selectedIndexes.length);
});
_loadNextPageData(page + 1, index, item, isUpdatePage: true);
}
} else {
/// tab页面
/// page == _tabKeys.length - 1 && _tabKeys.length == widget.maxPageNum
_selectedTabs[page] = item;
_selectedIndexes[page] = index;
_addTab(page + 1, index, item);
}
},
);
}
Widget _pageWidget(int page) {
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: _pagesData[page].length,
itemBuilder: (context, index) =>
_pageItemWidget(index, page, _pagesData[page][index]),
// separatorBuilder: (context, index) => Divider(height: 0.3, thickness: 0.3, color: Color(0xffdddddd), indent: 15, endIndent: 15,),
);
}
@override
void initState() {
super.initState();
_pagesData.add(widget.initialPageData);
_controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
_curvedAnimation = CurvedAnimation(parent: _controller, curve: Curves.ease)
..addStatusListener((state) {});
_sliderAnimation =
Tween<double>(begin: 0, end: 10).animate(_curvedAnimation);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
RenderBox tabBox =
_tabKeys.first.currentContext?.findRenderObject() as RenderBox;
_sliderFixMargin.value = (tabBox.size.width - _sliderWidth) / 2;
});
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AnimatedBuilder(
animation: _sliderAnimation!,
builder: (context, child) => Stack(
clipBehavior: Clip.hardEdge,
alignment: Alignment.bottomLeft,
children: [
Container(
width: MediaQuery.of(context).size.width,
child: Row(
children: _tabWidgets(),
),
),
ValueListenableBuilder<double>(
valueListenable: _sliderFixMargin,
builder: (_, margin, __) => Positioned(
left: margin + _sliderAnimation!.value,
child: Container(
key: _sliderKey,
width: _sliderWidth,
height: 2,
decoration: BoxDecoration(
color: widget.activeColor,
borderRadius: BorderRadius.circular(2)),
),
),
)
],
),
),
Expanded(
child: PageView.builder(
itemCount: _pagesData.length,
controller: _pageController,
itemBuilder: (context, index) => _pageWidget(index),
onPageChanged: (position) {
if (!_isClickAndMoveTab) {
_moveSlider(position, movePage: false);
}
if (_currentSelectPage == position) {
_isClickAndMoveTab = false;
}
},
),
)
],
);
}
}
class CascadeController extends GetxController {
late List<Item> items = [];
@override
void onInit() {
items = [];
for (int i = 0; i < 5; i++) {
Item item0 = Item();
item0.name = "name_$i";
item0.code = "code_$i";
List<Item> children1 = [];
for (int j = 0; j < 3; j++) {
Item item1 = Item();
item1.name = "name_${i}_$j";
item1.code = "code_${i}_$j";
List<Item> children2 = [];
for (int k = 0; k < 7; k++) {
Item item2 = Item();
item2.name = "name_${i}_${j}_$k";
item2.code = "code_${i}_${j}_$k";
// 3
item2.children = [];
children2.add(item2);
}
// 2
item1.children = children2;
children1.add(item1);
}
// 1
item0.children = children1;
items.add(item0);
super.onInit();
}
}
late final _CascadePickerState _state;
_setState(_CascadePickerState state) {
_state = state;
}
List<String> get selectedTitles => _state._selectedTabs;
List<int> get selectedIndexes => _state._selectedIndexes;
bool isCompleted() =>
!_state._selectedTabs.contains(_CascadePickerState._newTabName);
}

View File

@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class SkDialogHeader extends StatelessWidget {
final String title;
final Widget? leading;
final Widget? trailing;
const SkDialogHeader({
super.key,
required this.title,
this.leading,
this.trailing,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [AppTheme.primaryColorLight, AppTheme.primaryColor])),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
trailing ??
SizedBox(
width: ScreenAdaper.width(100),
),
Expanded(
child: Text(
title,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: ScreenAdaper.height(30),
color: AppTheme.nearlyWhite,
fontWeight: FontWeight.w600),
),
),
trailing ??
Container(
width: ScreenAdaper.width(100),
child: IconButton(
onPressed: () {
Get.back();
},
icon: Icon(
Icons.close,
size: ScreenAdaper.sp(45),
color: AppTheme.nearlyWhite,
)),
)
],
),
);
}
}

View File

@ -1,24 +1,48 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/bg_color.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class SkInk extends StatelessWidget { class SkInk extends StatelessWidget {
final Widget? child; final Widget? child;
final void Function()? onTap; final void Function()? onTap;
final BorderRadius? borderRadius; final BorderRadius? borderRadius;
final BoxBorder? border; final BoxBorder? border;
final Color? color;
final EdgeInsets? padding;
final EdgeInsets? margin;
final Gradient? gradient;
const SkInk( const SkInk(
{super.key, this.child, this.onTap, this.borderRadius, this.border}); {super.key,
this.child,
this.onTap,
this.color,
this.borderRadius,
this.border,
this.gradient,
this.margin,
this.padding});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return Container(
margin: margin,
child: ClipRRect(
borderRadius: borderRadius ?? BorderRadius.zero,
child: Material(
child: Ink( child: Ink(
padding: padding,
decoration: BoxDecoration( decoration: BoxDecoration(
border: border ?? Border.all(color: AppTheme.grey.withOpacity(0.8)), border: border,
color: color,
gradient: gradient,
borderRadius: borderRadius, borderRadius: borderRadius,
), ),
child: child: InkWell(
InkWell(borderRadius: borderRadius, onTap: onTap, child: child)), borderRadius: borderRadius, onTap: onTap, child: child)),
),
),
); );
} }
} }

View File

@ -1,4 +1,3 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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';

View File

@ -20,6 +20,7 @@ class SkTextInput extends StatefulWidget {
final Widget? suffixIcon; final Widget? suffixIcon;
final InputBorder? border; final InputBorder? border;
final FloatingLabelBehavior? floatingLabelBehavior; final FloatingLabelBehavior? floatingLabelBehavior;
final TextInputType? keyboardType;
const SkTextInput( const SkTextInput(
{super.key, {super.key,
required this.textController, required this.textController,
@ -28,6 +29,7 @@ class SkTextInput extends StatefulWidget {
this.onFieldSubmitted, this.onFieldSubmitted,
this.isRequired = false, this.isRequired = false,
this.onTapOutside, this.onTapOutside,
this.keyboardType,
this.labelText, this.labelText,
this.prefix, this.prefix,
this.suffixIcon, this.suffixIcon,
@ -79,6 +81,7 @@ class _SkTextInputState extends State<SkTextInput> {
widget.onTap!(focusNode); widget.onTap!(focusNode);
} }
}, },
keyboardType: widget.keyboardType,
onFieldSubmitted: widget.onFieldSubmitted, onFieldSubmitted: widget.onFieldSubmitted,
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
validator: widget.validator, validator: widget.validator,

View File

@ -54,11 +54,17 @@ class _FadeInCacheImageState extends State<FadeInCacheImage> {
Widget buildImg(String? url) { Widget buildImg(String? url) {
return CachedNetworkImage( return CachedNetworkImage(
alignment: Alignment.center,
imageUrl: url ?? '', imageUrl: url ?? '',
width: widget.width, width: widget.width,
height: widget.height, height: widget.height,
fit: widget.fit, imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
image: DecorationImage(
image: imageProvider,
fit: widget.fit ?? BoxFit.cover,
),
),
),
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
decoration: const BoxDecoration(color: AppTheme.grey), decoration: const BoxDecoration(color: AppTheme.grey),
child: const CupertinoActivityIndicator(), child: const CupertinoActivityIndicator(),
@ -70,12 +76,10 @@ class _FadeInCacheImageState extends State<FadeInCacheImage> {
Widget defaultImg() { Widget defaultImg() {
return Container( return Container(
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(), borderRadius: BorderRadius.circular(15)),
width: widget.width, width: widget.width,
height: widget.height, height: widget.height,
child: Icon(Icons.image_not_supported, child: Icon(Icons.image_not_supported,
size: ScreenAdaper.height((widget.width ?? 200) * 3 / 4), size: ScreenAdaper.height((widget.width ?? 200)),
color: AppTheme.grey), color: AppTheme.grey),
); );
} }

View File

@ -3,38 +3,29 @@ import 'package:loading_animation_widget/loading_animation_widget.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/util/screen_adaper_util.dart'; import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/core/sk_ink.dart';
class GradientButton extends StatelessWidget { class GradientButton extends StatelessWidget {
final VoidCallback? onPressed; final VoidCallback? onPressed;
final bool isLoading; final bool isLoading;
final String buttonText; final String buttonText;
final Icon? icon; final Icon? icon;
final BorderRadiusGeometry? borderRadius;
const GradientButton( const GradientButton(
{super.key, {super.key,
this.buttonText = TextEnum.createInventoryInOutBtnText, this.buttonText = TextEnum.createInventoryInOutBtnText,
this.onPressed, this.onPressed,
this.icon, this.icon,
this.borderRadius,
this.isLoading = false}); this.isLoading = false});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SkInk(
width: double.infinity, onTap: onPressed ?? () {},
child: ElevatedButton( gradient: const LinearGradient(
style: ButtonStyle( colors: [AppTheme.primaryColorLight, AppTheme.primaryColor]),
backgroundColor: child: SizedBox(
MaterialStateProperty.all<Color>(AppTheme.primaryColor), height: ScreenAdaper.height(80),
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
EdgeInsets.symmetric(vertical: ScreenAdaper.height(10)),
),
minimumSize: MaterialStateProperty.all<Size>(
Size(double.infinity, ScreenAdaper.height(80))),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
onPressed: onPressed ?? () => {},
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -58,8 +49,54 @@ class GradientButton extends StatelessWidget {
fontSize: ScreenAdaper.height(25), fontSize: ScreenAdaper.height(25),
), ),
) )
]), ])),
),
); );
// ElevatedButton(
// style: ButtonStyle(
// backgroundColor:
// MaterialStateProperty.all<Color>(AppTheme.primaryColor),
// minimumSize: MaterialStateProperty.all<Size>(
// Size(double.infinity, ScreenAdaper.height(80))),
// shape: MaterialStateProperty.all<RoundedRectangleBorder>(
// RoundedRectangleBorder(
// borderRadius: borderRadius ?? BorderRadius.circular(10),
// ),
// ),
// ),
// onPressed: onPressed ?? () => {},
// child: Text(
// buttonText,
// style: TextStyle(
// color: AppTheme.nearlyWhite,
// fontWeight: FontWeight.bold,
// fontSize: ScreenAdaper.height(25),
// ),
// ),
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// if (icon != null) ...[
// icon!,
// SizedBox(
// width: ScreenAdaper.width(10),
// )
// ],
// isLoading
// ? LoadingAnimationWidget.fourRotatingDots(
// color: AppTheme.nearlyWhite,
// size: ScreenAdaper.height(40),
// )
// : Text(
// buttonText,
// style: TextStyle(
// color: AppTheme.nearlyWhite,
// fontWeight: FontWeight.bold,
// fontSize: ScreenAdaper.height(25),
// ),
// )
// ]),
// );
} }
} }

View File

@ -14,10 +14,13 @@ class LoadingIndicator extends StatelessWidget {
return common return common
? LoadingAnimationWidget.fourRotatingDots( ? LoadingAnimationWidget.fourRotatingDots(
color: AppTheme.primaryColorLight, size: ScreenAdaper.height(45)) color: AppTheme.primaryColorLight, size: ScreenAdaper.height(45))
: CupertinoActivityIndicator( : Container(
padding: EdgeInsets.all(ScreenAdaper.height(10)),
child: CupertinoActivityIndicator(
animating: animating, animating: animating,
color: AppTheme.primaryColor, color: AppTheme.primaryColor,
radius: ScreenAdaper.sp(25), radius: ScreenAdaper.sp(25),
),
); );
} }
} }

View File

@ -1,9 +1,11 @@
import 'dart:io'; import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:sk_base_mobile/store/auth.store.dart'; import 'package:sk_base_mobile/store/auth.store.dart';
import 'package:sk_base_mobile/app_theme.dart'; import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/widgets/fade_in_cache_image.dart';
import '../util/util.dart'; import '../util/util.dart';
@ -15,17 +17,7 @@ class MyAvatarWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
children: [ children: [
Obx(() => Container( Obx(() => _buildAvatar()),
decoration: BoxDecoration(
image:
DecorationImage(fit: BoxFit.cover, image: _buildImage()),
border: Border.all(
color: AppTheme.white, width: ScreenAdaper.sp(2)),
borderRadius: BorderRadius.circular(50)),
height: ScreenAdaper.width(150),
width: ScreenAdaper.width(150),
child: const SizedBox(),
)),
Positioned( Positioned(
bottom: 0, bottom: 0,
right: 0, right: 0,
@ -41,11 +33,16 @@ class MyAvatarWidget extends StatelessWidget {
); );
} }
dynamic _buildImage() { dynamic _buildAvatar() {
return _controller.uploadImgFilePath.value.isNotEmpty return _controller.uploadImgFilePath.value.isNotEmpty
? FileImage(File(_controller.uploadImgFilePath.value)) ? FileImage(File(_controller.uploadImgFilePath.value))
: NetworkImage( : ClipRRect(
AuthStore.to.userInfo.value.avatar ?? '', borderRadius: BorderRadius.circular(ScreenAdaper.sp(120)),
child: FadeInCacheImage(
height: ScreenAdaper.height(120),
width: ScreenAdaper.height(120),
url: MediaUtil.getMediaUrl(AuthStore.to.userInfo.value.avatar),
),
); );
} }
// Widget getShowImg() { // Widget getShowImg() {

View File

@ -67,7 +67,7 @@ class UpgradeConfirm extends StatelessWidget {
SizedBox( SizedBox(
height: ScreenAdaper.height(30), height: ScreenAdaper.height(30),
), ),
Container( SizedBox(
width: ScreenAdaper.height(400), width: ScreenAdaper.height(400),
child: GradientButton( child: GradientButton(
buttonText: '去更新', buttonText: '去更新',

View File

@ -1005,6 +1005,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.2"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e"
url: "https://pub.dev"
source: hosted
version: "6.2.5"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
url: "https://pub.dev"
source: hosted
version: "6.3.0"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://pub.dev"
source: hosted
version: "6.2.5"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
url: "https://pub.dev"
source: hosted
version: "3.1.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
url: "https://pub.dev"
source: hosted
version: "3.1.0"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
url: "https://pub.dev"
source: hosted
version: "2.2.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
url: "https://pub.dev"
source: hosted
version: "3.1.1"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:

View File

@ -67,6 +67,7 @@ dependencies:
pinyin: ^3.2.0 pinyin: ^3.2.0
math_expressions: ^2.4.0 math_expressions: ^2.4.0
install_plugin: ^2.1.0 install_plugin: ^2.1.0
url_launcher: ^6.2.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter