feat: hr manage
This commit is contained in:
parent
93a30355cd
commit
cee31e6896
|
@ -45,5 +45,21 @@
|
|||
android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<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>
|
|
@ -3,7 +3,7 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<true />
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
|
@ -25,7 +25,7 @@
|
|||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<true />
|
||||
<key>Launch screen interface file base name</key>
|
||||
<string>$(LAUNCH_SCREEN_STORYBOARD)</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
|
@ -35,13 +35,18 @@
|
|||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>App需要您的同意,才能访问相册</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<true />
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>sms</string>
|
||||
<string>tel</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen.storyboard</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
<false />
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
@ -56,6 +61,6 @@
|
|||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<false />
|
||||
</dict>
|
||||
</plist>
|
|
@ -22,13 +22,18 @@ class Api {
|
|||
);
|
||||
}
|
||||
|
||||
// 获取个人信息
|
||||
static Future<Response> getUserInfo() {
|
||||
// 获取我的个人信息
|
||||
static Future<Response> getMyProfile() {
|
||||
return DioService.dio.get(
|
||||
Urls.userInfo,
|
||||
);
|
||||
}
|
||||
|
||||
// 获取个人信息
|
||||
static Future<Response> getUserInfo(int userid) {
|
||||
return DioService.dio.get('${Urls.userInfo}/$userid');
|
||||
}
|
||||
|
||||
// 分页获取项目列表
|
||||
static Future<Response<PaginationData>> getProjects(Map params) {
|
||||
return DioService.dio.get<PaginationData>(Urls.projects,
|
||||
|
|
|
@ -26,7 +26,7 @@ class AppTheme {
|
|||
static const Color dangerColor = Colors.red;
|
||||
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 scaffoldBackgroundColor = Color(0XFFe9f0fd);
|
||||
static const Color inputFillColor = Color(0xFFf5f8ff);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ class Urls {
|
|||
static String deleteAccount = 'user/deleteAccount';
|
||||
static String saveUserInfo = 'user/saveUserInfo';
|
||||
static String sysUser = 'system/users';
|
||||
static String userInfo = 'account/profile';
|
||||
static String myProfile = 'account/profile';
|
||||
static String projects = 'project';
|
||||
static String products = 'product';
|
||||
static String inventoryInout = 'materials-in-out';
|
||||
|
@ -15,4 +15,5 @@ class Urls {
|
|||
static String uploadAttachemnt = 'tools/upload';
|
||||
static String systemParamConfig = 'system/param-config';
|
||||
static String accountMenus = 'account/menus';
|
||||
static String userInfo = 'system/users';
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
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/inventory/inventory.dart';
|
||||
import 'package:sk_base_mobile/screens/login/login.dart';
|
||||
|
@ -14,6 +16,7 @@ class RouteConfig {
|
|||
static const String inventory = '/inventory';
|
||||
static const String saleQuotation = '/sale_quotation';
|
||||
static const String hrManage = '/hr_manage';
|
||||
static const String employeeDetail = '/employee_detail';
|
||||
|
||||
static final List<GetPage> getPages = [
|
||||
GetPage(name: login, page: () => LoginScreen()),
|
||||
|
@ -21,6 +24,7 @@ class RouteConfig {
|
|||
GetPage(name: userinfo, page: () => UserInfoPage()),
|
||||
GetPage(name: inventory, page: () => const InventoryPage()),
|
||||
GetPage(name: saleQuotation, page: () => SaleQuotationPage()),
|
||||
GetPage(name: hrManage, page: () => HrManagePage())
|
||||
GetPage(name: hrManage, page: () => HrManagePage()),
|
||||
GetPage(name: employeeDetail, page: () => EmployeeDetail())
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 {}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -3,16 +3,26 @@ 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/config.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/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/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/snack_bar.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';
|
||||
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 {
|
||||
final controller = Get.put(HrManageController());
|
||||
|
@ -20,14 +30,21 @@ class HrManagePage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// return Column(
|
||||
// children: [
|
||||
// Expanded(
|
||||
// child: Material(
|
||||
// child: EditUserInfo(),
|
||||
// ))
|
||||
// ],
|
||||
// );
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
// 取消焦点
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: Color(0xFFe9f0fd),
|
||||
appBar: SkAppbar(
|
||||
appBar: const SkAppbar(
|
||||
backgroundColor: AppTheme.nearlyWhite,
|
||||
iconAndTextColor: AppTheme.black,
|
||||
title: '人事管理',
|
||||
|
@ -44,9 +61,11 @@ class HrManagePage extends StatelessWidget {
|
|||
child: Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: ScreenAdaper.height(defaultPadding),
|
||||
vertical: ScreenAdaper.height(defaultPadding)),
|
||||
),
|
||||
child: Obx(
|
||||
() => SmartRefresher(
|
||||
() => controller.loading.value
|
||||
? const LoadingIndicator(common: true)
|
||||
: SmartRefresher(
|
||||
enablePullDown: true,
|
||||
enablePullUp: true,
|
||||
controller: controller.refreshController,
|
||||
|
@ -54,9 +73,12 @@ class HrManagePage extends StatelessWidget {
|
|||
onRefresh: controller.onRefresh,
|
||||
child: controller.list.isEmpty
|
||||
? const Center(
|
||||
child: Empty(text: '暂无数据'),
|
||||
child: Empty(text: '暂无员工'),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical:
|
||||
ScreenAdaper.height(defaultPadding)),
|
||||
itemBuilder: (context, index) {
|
||||
return buildUserCard(index);
|
||||
},
|
||||
|
@ -75,11 +97,8 @@ class HrManagePage extends StatelessWidget {
|
|||
}, 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: [
|
||||
padding: EdgeInsets.all(ScreenAdaper.width(20)),
|
||||
child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: ScreenAdaper.height(70),
|
||||
|
@ -132,25 +151,29 @@ class HrManagePage extends StatelessWidget {
|
|||
}
|
||||
|
||||
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(
|
||||
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(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||
child: FadeInCacheImage(
|
||||
height: ScreenAdaper.height(80),
|
||||
image: NetworkImage(
|
||||
'https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=1743369777')),
|
||||
width: ScreenAdaper.height(80),
|
||||
url:
|
||||
MediaUtil.getMediaUrl(controller.list[index].avatar)),
|
||||
),
|
||||
SizedBox(
|
||||
width: ScreenAdaper.height(defaultPadding),
|
||||
|
@ -179,7 +202,8 @@ class HrManagePage extends StatelessWidget {
|
|||
runSpacing: ScreenAdaper.height(5),
|
||||
children: [
|
||||
...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
|
||||
/// 电话
|
||||
buildActionButton(
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
if (controller.list[index].phone == null) {
|
||||
SnackBarUtil().info('该员工没有录入手机号');
|
||||
return;
|
||||
}
|
||||
DeviceUtil.callPhone(controller.list[index].phone!);
|
||||
},
|
||||
icon: Icons.phone,
|
||||
),
|
||||
SizedBox(
|
||||
|
@ -197,7 +227,13 @@ class HrManagePage extends StatelessWidget {
|
|||
|
||||
/// 邮件
|
||||
buildActionButton(
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
if (controller.list[index].email == null) {
|
||||
SnackBarUtil().info('该员工没有录入邮箱');
|
||||
return;
|
||||
}
|
||||
DeviceUtil.sendEmail(controller.list[index].email!);
|
||||
},
|
||||
icon: Icons.email_outlined,
|
||||
),
|
||||
SizedBox(
|
||||
|
@ -206,7 +242,15 @@ class HrManagePage extends StatelessWidget {
|
|||
|
||||
/// 编辑
|
||||
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,
|
||||
)
|
||||
],
|
||||
|
@ -225,6 +269,7 @@ class HrManagePage extends StatelessWidget {
|
|||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -233,14 +278,14 @@ class HrManagePage extends StatelessWidget {
|
|||
required IconData icon,
|
||||
}) {
|
||||
return SkInk(
|
||||
onTap: () {},
|
||||
onTap: onTap,
|
||||
border: Border.all(color: AppTheme.grey.withOpacity(0.8)),
|
||||
borderRadius: BorderRadius.circular(ScreenAdaper.sp(30)),
|
||||
borderRadius: BorderRadius.circular(ScreenAdaper.sp(40)),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(ScreenAdaper.height(5)),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: ScreenAdaper.height(35),
|
||||
size: ScreenAdaper.height(40),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -251,10 +296,27 @@ class HrManageController extends GetxController {
|
|||
RxList<UserInfoModel> list = RxList([]);
|
||||
RxString searchKey = ''.obs;
|
||||
final searchBarTextConroller = TextEditingController();
|
||||
RefreshController refreshController = RefreshController(initialRefresh: true);
|
||||
RefreshController refreshController =
|
||||
RefreshController(initialRefresh: false);
|
||||
int page = 1;
|
||||
int limit = 15;
|
||||
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 {
|
||||
if (isRefresh == true) {
|
||||
page = 1;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.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 {
|
||||
final bool isPage;
|
||||
|
|
|
@ -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/image_preview.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 {
|
||||
final int inventoryInoutId;
|
||||
|
@ -22,23 +23,7 @@ class InventoryInoutInfo extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
child: Column(children: [
|
||||
Container(
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SkDialogHeader(title: '出入库详情'),
|
||||
Expanded(
|
||||
child: Obx(() => controller.inventoryInoutInfo.value == null
|
||||
? const Center(child: LoadingIndicator(common: true))
|
||||
|
|
|
@ -15,7 +15,7 @@ class MineController extends GetxController {
|
|||
|
||||
@override
|
||||
void onInit() {
|
||||
// AuthStore.to.getUserInfo();
|
||||
// AuthStore.to.getMyProfile();
|
||||
|
||||
super.onInit();
|
||||
}
|
||||
|
|
|
@ -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/gradient_button.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 {
|
||||
final NewInventoryInoutController controller;
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:sk_base_mobile/app_theme.dart';
|
||||
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||
import 'package:sk_base_mobile/widgets/gradient_button.dart';
|
||||
import 'package:sk_base_mobile/widgets/sk_appbar.dart';
|
||||
import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
|
||||
|
||||
class SaleQuotationEndDrawer extends StatelessWidget {
|
||||
const SaleQuotationEndDrawer({super.key});
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:ffi';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.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_text_input.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:math_expressions/math_expressions.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
class SaleQuotationPage extends StatelessWidget {
|
||||
SaleQuotationPage({super.key});
|
||||
|
|
|
@ -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/util/screen_adaper_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 {
|
||||
WorkBenchPage({super.key});
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sk_base_mobile/models/workbench.model.dart';
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class AuthStore extends GetxService {
|
|||
|
||||
if (token != null) {
|
||||
if (preUserInfo != null) {
|
||||
await getUserInfo();
|
||||
await getMyProfile();
|
||||
LoggerUtil().info('[Store-Auth] userId: ${userInfo.value.id}');
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ class AuthStore extends GetxService {
|
|||
await StorageService.to
|
||||
.setString(CacheKeys.token, auth.token!, isWithUser: false);
|
||||
}
|
||||
await getUserInfo();
|
||||
await getMyProfile();
|
||||
await getCommonInfo();
|
||||
Get.offNamed(RouteConfig.home);
|
||||
}
|
||||
|
@ -105,9 +105,9 @@ class AuthStore extends GetxService {
|
|||
.setString(CacheKeys.userInfo, jsonEncode(newInfo), isWithUser: false);
|
||||
}
|
||||
|
||||
Future<void> getUserInfo() async {
|
||||
Future<void> getMyProfile() async {
|
||||
try {
|
||||
final response = await Api.getUserInfo();
|
||||
final response = await Api.getMyProfile();
|
||||
if (response.data != null) {
|
||||
UserInfoModel userInfo = UserInfoModel.fromJson(response.data);
|
||||
await updateUserInfoState(userInfo);
|
||||
|
@ -122,7 +122,7 @@ class AuthStore extends GetxService {
|
|||
try {
|
||||
final res = await Api.saveUserInfo(data);
|
||||
if (res.data != null) {
|
||||
await getUserInfo();
|
||||
await getMyProfile();
|
||||
SnackBarUtil().success('Save successfully.');
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,7 +1,30 @@
|
|||
import 'dart:io';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
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 {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
|
|
@ -5,10 +5,19 @@ import 'package:dio/dio.dart';
|
|||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||
import 'package:sk_base_mobile/apis/index.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/services/service.dart';
|
||||
|
||||
class MediaUtil {
|
||||
static String? getMediaUrl(String? url) {
|
||||
if ((url ?? '').isEmpty) {
|
||||
return null;
|
||||
} else {
|
||||
return '${GloablConfig.OSS_URL}$url';
|
||||
}
|
||||
}
|
||||
|
||||
//拍照
|
||||
Future<XFile?> getImageFromCamera(
|
||||
{double? maxWidth, bool isFront = false}) async {
|
||||
|
@ -52,6 +61,15 @@ class MediaUtil {
|
|||
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(
|
||||
// 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 {
|
||||
// if (AppInfoModel().isCameraing) {
|
||||
|
|
|
@ -167,7 +167,7 @@ class ModalUtil {
|
|||
double? height,
|
||||
Offset? offset}) {
|
||||
return Get.generalDialog(
|
||||
barrierLabel: "productPicker",
|
||||
barrierLabel: "generalDialog",
|
||||
barrierDismissible: true,
|
||||
transitionDuration: const Duration(milliseconds: 400),
|
||||
pageBuilder: (_, __, ___) {
|
||||
|
|
|
@ -74,7 +74,7 @@ class SnackBarUtil {
|
|||
borderRadius: 15,
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: ScreenAdaper.height(15), vertical: 0),
|
||||
duration: const Duration(seconds: 1),
|
||||
duration: const Duration(seconds: 2),
|
||||
dismissDirection: DismissDirection.horizontal,
|
||||
forwardAnimationCurve: Curves.fastLinearToSlowEaseIn,
|
||||
reverseAnimationCurve: Curves.linearToEaseOut);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sk_base_mobile/app_theme.dart';
|
||||
import 'package:sk_base_mobile/util/util.dart';
|
||||
|
||||
class SkAppbar extends StatelessWidget implements PreferredSizeWidget {
|
|
@ -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设为true,防止滑动PageView
|
||||
/// 时_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 {
|
||||
/// 选择不同的item,更新tab 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);
|
||||
}
|
|
@ -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,
|
||||
)),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,24 +1,48 @@
|
|||
import 'package:flutter/material.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 {
|
||||
final Widget? child;
|
||||
final void Function()? onTap;
|
||||
final BorderRadius? borderRadius;
|
||||
final BoxBorder? border;
|
||||
|
||||
final Color? color;
|
||||
final EdgeInsets? padding;
|
||||
final EdgeInsets? margin;
|
||||
final Gradient? gradient;
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
return Container(
|
||||
margin: margin,
|
||||
child: ClipRRect(
|
||||
borderRadius: borderRadius ?? BorderRadius.zero,
|
||||
child: Material(
|
||||
child: Ink(
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
border: border ?? Border.all(color: AppTheme.grey.withOpacity(0.8)),
|
||||
border: border,
|
||||
color: color,
|
||||
gradient: gradient,
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child:
|
||||
InkWell(borderRadius: borderRadius, onTap: onTap, child: child)),
|
||||
child: InkWell(
|
||||
borderRadius: borderRadius, onTap: onTap, child: child)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
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';
|
||||
|
|
|
@ -20,6 +20,7 @@ class SkTextInput extends StatefulWidget {
|
|||
final Widget? suffixIcon;
|
||||
final InputBorder? border;
|
||||
final FloatingLabelBehavior? floatingLabelBehavior;
|
||||
final TextInputType? keyboardType;
|
||||
const SkTextInput(
|
||||
{super.key,
|
||||
required this.textController,
|
||||
|
@ -28,6 +29,7 @@ class SkTextInput extends StatefulWidget {
|
|||
this.onFieldSubmitted,
|
||||
this.isRequired = false,
|
||||
this.onTapOutside,
|
||||
this.keyboardType,
|
||||
this.labelText,
|
||||
this.prefix,
|
||||
this.suffixIcon,
|
||||
|
@ -79,6 +81,7 @@ class _SkTextInputState extends State<SkTextInput> {
|
|||
widget.onTap!(focusNode);
|
||||
}
|
||||
},
|
||||
keyboardType: widget.keyboardType,
|
||||
onFieldSubmitted: widget.onFieldSubmitted,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: widget.validator,
|
||||
|
|
|
@ -54,11 +54,17 @@ class _FadeInCacheImageState extends State<FadeInCacheImage> {
|
|||
|
||||
Widget buildImg(String? url) {
|
||||
return CachedNetworkImage(
|
||||
alignment: Alignment.center,
|
||||
imageUrl: url ?? '',
|
||||
width: widget.width,
|
||||
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(
|
||||
decoration: const BoxDecoration(color: AppTheme.grey),
|
||||
child: const CupertinoActivityIndicator(),
|
||||
|
@ -70,12 +76,10 @@ class _FadeInCacheImageState extends State<FadeInCacheImage> {
|
|||
Widget defaultImg() {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(), borderRadius: BorderRadius.circular(15)),
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: Icon(Icons.image_not_supported,
|
||||
size: ScreenAdaper.height((widget.width ?? 200) * 3 / 4),
|
||||
size: ScreenAdaper.height((widget.width ?? 200)),
|
||||
color: AppTheme.grey),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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/constants/constants.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 {
|
||||
final VoidCallback? onPressed;
|
||||
final bool isLoading;
|
||||
final String buttonText;
|
||||
final Icon? icon;
|
||||
final BorderRadiusGeometry? borderRadius;
|
||||
const GradientButton(
|
||||
{super.key,
|
||||
this.buttonText = TextEnum.createInventoryInOutBtnText,
|
||||
this.onPressed,
|
||||
this.icon,
|
||||
this.borderRadius,
|
||||
this.isLoading = false});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.all<Color>(AppTheme.primaryColor),
|
||||
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 ?? () => {},
|
||||
return SkInk(
|
||||
onTap: onPressed ?? () {},
|
||||
gradient: const LinearGradient(
|
||||
colors: [AppTheme.primaryColorLight, AppTheme.primaryColor]),
|
||||
child: SizedBox(
|
||||
height: ScreenAdaper.height(80),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
@ -58,8 +49,54 @@ class GradientButton extends StatelessWidget {
|
|||
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),
|
||||
// ),
|
||||
// )
|
||||
// ]),
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,10 +14,13 @@ class LoadingIndicator extends StatelessWidget {
|
|||
return common
|
||||
? LoadingAnimationWidget.fourRotatingDots(
|
||||
color: AppTheme.primaryColorLight, size: ScreenAdaper.height(45))
|
||||
: CupertinoActivityIndicator(
|
||||
: Container(
|
||||
padding: EdgeInsets.all(ScreenAdaper.height(10)),
|
||||
child: CupertinoActivityIndicator(
|
||||
animating: animating,
|
||||
color: AppTheme.primaryColor,
|
||||
radius: ScreenAdaper.sp(25),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import 'dart:io';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:sk_base_mobile/store/auth.store.dart';
|
||||
import 'package:sk_base_mobile/app_theme.dart';
|
||||
import 'package:sk_base_mobile/widgets/fade_in_cache_image.dart';
|
||||
|
||||
import '../util/util.dart';
|
||||
|
||||
|
@ -15,17 +17,7 @@ class MyAvatarWidget extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Obx(() => Container(
|
||||
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(),
|
||||
)),
|
||||
Obx(() => _buildAvatar()),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
|
@ -41,11 +33,16 @@ class MyAvatarWidget extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
dynamic _buildImage() {
|
||||
dynamic _buildAvatar() {
|
||||
return _controller.uploadImgFilePath.value.isNotEmpty
|
||||
? FileImage(File(_controller.uploadImgFilePath.value))
|
||||
: NetworkImage(
|
||||
AuthStore.to.userInfo.value.avatar ?? '',
|
||||
: ClipRRect(
|
||||
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() {
|
||||
|
|
|
@ -67,7 +67,7 @@ class UpgradeConfirm extends StatelessWidget {
|
|||
SizedBox(
|
||||
height: ScreenAdaper.height(30),
|
||||
),
|
||||
Container(
|
||||
SizedBox(
|
||||
width: ScreenAdaper.height(400),
|
||||
child: GradientButton(
|
||||
buttonText: '去更新',
|
||||
|
|
64
pubspec.lock
64
pubspec.lock
|
@ -1005,6 +1005,70 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -67,6 +67,7 @@ dependencies:
|
|||
pinyin: ^3.2.0
|
||||
math_expressions: ^2.4.0
|
||||
install_plugin: ^2.1.0
|
||||
url_launcher: ^6.2.5
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
|
Loading…
Reference in New Issue