feat: hr manage
This commit is contained in:
parent
93a30355cd
commit
cee31e6896
|
@ -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>
|
|
@ -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>
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: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: '人事管理',
|
||||||
|
@ -43,25 +60,30 @@ class HrManagePage extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
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
|
||||||
enablePullDown: true,
|
? const LoadingIndicator(common: true)
|
||||||
enablePullUp: true,
|
: SmartRefresher(
|
||||||
controller: controller.refreshController,
|
enablePullDown: true,
|
||||||
onLoading: controller.onLoading,
|
enablePullUp: true,
|
||||||
onRefresh: controller.onRefresh,
|
controller: controller.refreshController,
|
||||||
child: controller.list.isEmpty
|
onLoading: controller.onLoading,
|
||||||
? const Center(
|
onRefresh: controller.onRefresh,
|
||||||
child: Empty(text: '暂无数据'),
|
child: controller.list.isEmpty
|
||||||
)
|
? const Center(
|
||||||
: ListView.builder(
|
child: Empty(text: '暂无员工'),
|
||||||
itemBuilder: (context, index) {
|
)
|
||||||
return buildUserCard(index);
|
: ListView.builder(
|
||||||
},
|
padding: EdgeInsets.symmetric(
|
||||||
itemCount: controller.list.length,
|
vertical:
|
||||||
)),
|
ScreenAdaper.height(defaultPadding)),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return buildUserCard(index);
|
||||||
|
},
|
||||||
|
itemCount: controller.list.length,
|
||||||
|
)),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -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,98 +151,124 @@ class HrManagePage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildUserCard(int index) {
|
Widget buildUserCard(int index) {
|
||||||
return Container(
|
return SkInk(
|
||||||
padding: EdgeInsets.symmetric(
|
onTap: () {
|
||||||
horizontal: ScreenAdaper.height(defaultPadding),
|
Get.toNamed(RouteConfig.employeeDetail,
|
||||||
vertical: ScreenAdaper.height(defaultPadding)),
|
arguments: controller.list[index]);
|
||||||
|
},
|
||||||
margin: EdgeInsets.only(bottom: ScreenAdaper.height(defaultPadding)),
|
margin: EdgeInsets.only(bottom: ScreenAdaper.height(defaultPadding)),
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)),
|
||||||
color: AppTheme.white,
|
child: Container(
|
||||||
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15))),
|
padding: EdgeInsets.symmetric(
|
||||||
child: Column(
|
horizontal: ScreenAdaper.height(defaultPadding),
|
||||||
children: [
|
vertical: ScreenAdaper.height(defaultPadding)),
|
||||||
Row(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// 头像
|
Row(
|
||||||
ClipRRect(
|
children: [
|
||||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
// 头像
|
||||||
child: Image(
|
ClipRRect(
|
||||||
height: ScreenAdaper.height(80),
|
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||||
image: NetworkImage(
|
child: FadeInCacheImage(
|
||||||
'https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=1743369777')),
|
height: ScreenAdaper.height(80),
|
||||||
),
|
width: ScreenAdaper.height(80),
|
||||||
SizedBox(
|
url:
|
||||||
width: ScreenAdaper.height(defaultPadding),
|
MediaUtil.getMediaUrl(controller.list[index].avatar)),
|
||||||
),
|
),
|
||||||
// 中间信息
|
SizedBox(
|
||||||
Expanded(
|
width: ScreenAdaper.height(defaultPadding),
|
||||||
child: Column(
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// 中间信息
|
||||||
children: [
|
Expanded(
|
||||||
Text('${controller.list[index].nickname}',
|
child: Column(
|
||||||
style: TextStyle(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
fontSize: ScreenAdaper.height(30),
|
children: [
|
||||||
fontWeight: FontWeight.w600)),
|
Text('${controller.list[index].nickname}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.height(30),
|
||||||
|
fontWeight: FontWeight.w600)),
|
||||||
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: ScreenAdaper.height(5),
|
height: ScreenAdaper.height(5),
|
||||||
), // role
|
), // role
|
||||||
Text('${controller.list[index].dept?.name}',
|
Text('${controller.list[index].dept?.name}',
|
||||||
style: TextStyle(color: AppTheme.grey)),
|
style: TextStyle(color: AppTheme.grey)),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: ScreenAdaper.height(5),
|
height: ScreenAdaper.height(5),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
spacing: ScreenAdaper.height(5),
|
spacing: ScreenAdaper.height(5),
|
||||||
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))
|
||||||
)),
|
],
|
||||||
],
|
)),
|
||||||
)),
|
],
|
||||||
|
)),
|
||||||
|
|
||||||
/// 右侧action
|
/// 右侧action
|
||||||
/// 电话
|
/// 电话
|
||||||
buildActionButton(
|
buildActionButton(
|
||||||
onTap: () {},
|
onTap: () {
|
||||||
icon: Icons.phone,
|
if (controller.list[index].phone == null) {
|
||||||
),
|
SnackBarUtil().info('该员工没有录入手机号');
|
||||||
SizedBox(
|
return;
|
||||||
width: ScreenAdaper.height(defaultPadding),
|
}
|
||||||
),
|
DeviceUtil.callPhone(controller.list[index].phone!);
|
||||||
|
},
|
||||||
|
icon: Icons.phone,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: ScreenAdaper.height(defaultPadding),
|
||||||
|
),
|
||||||
|
|
||||||
/// 邮件
|
/// 邮件
|
||||||
buildActionButton(
|
buildActionButton(
|
||||||
onTap: () {},
|
onTap: () {
|
||||||
icon: Icons.email_outlined,
|
if (controller.list[index].email == null) {
|
||||||
),
|
SnackBarUtil().info('该员工没有录入邮箱');
|
||||||
SizedBox(
|
return;
|
||||||
width: ScreenAdaper.height(defaultPadding),
|
}
|
||||||
),
|
DeviceUtil.sendEmail(controller.list[index].email!);
|
||||||
|
},
|
||||||
|
icon: Icons.email_outlined,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: ScreenAdaper.height(defaultPadding),
|
||||||
|
),
|
||||||
|
|
||||||
/// 编辑
|
/// 编辑
|
||||||
buildActionButton(
|
buildActionButton(
|
||||||
onTap: () {},
|
onTap: () {
|
||||||
icon: Icons.edit,
|
ModalUtil.showGeneralDialog(
|
||||||
)
|
content: EditUserInfo(
|
||||||
],
|
userId: controller.list[index].id!,
|
||||||
),
|
)).then((value) => Get.delete<EditUserInfoController>());
|
||||||
const Divider(),
|
|
||||||
Row(
|
// Get.toNamed(RouteConfig.employeeDetail,
|
||||||
children: [
|
// arguments: controller.list[index]);
|
||||||
Text(
|
},
|
||||||
'工龄: ${SkDateUtil.howLongAgo(controller.list[index].createdAt!)}'),
|
icon: Icons.edit,
|
||||||
const Spacer(),
|
)
|
||||||
const SkTag(
|
],
|
||||||
text: '在职',
|
),
|
||||||
color: Colors.green,
|
const Divider(),
|
||||||
)
|
Row(
|
||||||
],
|
children: [
|
||||||
)
|
Text(
|
||||||
],
|
'工龄: ${SkDateUtil.howLongAgo(controller.list[index].createdAt!)}'),
|
||||||
|
const Spacer(),
|
||||||
|
const SkTag(
|
||||||
|
text: '在职',
|
||||||
|
color: Colors.green,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -15,7 +15,7 @@ class MineController extends GetxController {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
// AuthStore.to.getUserInfo();
|
// AuthStore.to.getMyProfile();
|
||||||
|
|
||||||
super.onInit();
|
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/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;
|
||||||
|
|
|
@ -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});
|
||||||
|
|
|
@ -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});
|
||||||
|
|
|
@ -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});
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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: (_, __, ___) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
|
@ -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: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(
|
||||||
child: Ink(
|
margin: margin,
|
||||||
decoration: BoxDecoration(
|
child: ClipRRect(
|
||||||
border: border ?? Border.all(color: AppTheme.grey.withOpacity(0.8)),
|
borderRadius: borderRadius ?? BorderRadius.zero,
|
||||||
borderRadius: borderRadius,
|
child: Material(
|
||||||
),
|
child: Ink(
|
||||||
child:
|
padding: padding,
|
||||||
InkWell(borderRadius: borderRadius, onTap: onTap, child: child)),
|
decoration: BoxDecoration(
|
||||||
|
border: border,
|
||||||
|
color: color,
|
||||||
|
gradient: gradient,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: borderRadius, onTap: onTap, child: child)),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,63 +3,100 @@ 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>(
|
child: Row(
|
||||||
EdgeInsets.symmetric(vertical: ScreenAdaper.height(10)),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
minimumSize: MaterialStateProperty.all<Size>(
|
children: [
|
||||||
Size(double.infinity, ScreenAdaper.height(80))),
|
if (icon != null) ...[
|
||||||
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
icon!,
|
||||||
RoundedRectangleBorder(
|
SizedBox(
|
||||||
borderRadius: BorderRadius.circular(10),
|
width: ScreenAdaper.width(10),
|
||||||
),
|
)
|
||||||
),
|
],
|
||||||
),
|
isLoading
|
||||||
onPressed: onPressed ?? () => {},
|
? LoadingAnimationWidget.fourRotatingDots(
|
||||||
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,
|
color: AppTheme.nearlyWhite,
|
||||||
fontWeight: FontWeight.bold,
|
size: ScreenAdaper.height(40),
|
||||||
fontSize: ScreenAdaper.height(25),
|
)
|
||||||
),
|
: Text(
|
||||||
)
|
buttonText,
|
||||||
]),
|
style: TextStyle(
|
||||||
),
|
color: AppTheme.nearlyWhite,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
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
|
return common
|
||||||
? LoadingAnimationWidget.fourRotatingDots(
|
? LoadingAnimationWidget.fourRotatingDots(
|
||||||
color: AppTheme.primaryColorLight, size: ScreenAdaper.height(45))
|
color: AppTheme.primaryColorLight, size: ScreenAdaper.height(45))
|
||||||
: CupertinoActivityIndicator(
|
: Container(
|
||||||
animating: animating,
|
padding: EdgeInsets.all(ScreenAdaper.height(10)),
|
||||||
color: AppTheme.primaryColor,
|
child: CupertinoActivityIndicator(
|
||||||
radius: ScreenAdaper.sp(25),
|
animating: animating,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
radius: ScreenAdaper.sp(25),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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: '去更新',
|
||||||
|
|
64
pubspec.lock
64
pubspec.lock
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue