feat:适配多租户

This commit is contained in:
dzc committed 2025-04-22 15:15:46 +08:00
1 parent cdbb69f843
commit b370be9111
13 files changed
+175 -8

No files matched your search

+9
View File
@@ -56,4 +56,13 @@ abstract class UserApi {
?.map<ServiceAsyncFace>((e) => ServiceAsyncFace.fromJson(e))
.toList();
}
/// 获取租户列表
static Future<List<ServiceTenant>?> getTenantList() async {
final res = await HttpManage().dio.get("/admin/tenant/list");
if (res.data == null) return null;
return res.data
?.map<ServiceTenant>((e) => ServiceTenant.fromJson(e))
.toList();
}
}
@@ -126,3 +126,32 @@ class ServiceAsyncFace {
Map<String, dynamic> toJson() => _$ServiceAsyncFaceToJson(this);
}
@JsonSerializable()
class ServiceTenant {
/// id
final String? id;
/// 编号
final String? code;
/// 编号
final String? name;
final String? menuId;
final String? miniQr;
ServiceTenant({
this.id,
this.code,
this.name,
this.menuId,
this.miniQr,
});
factory ServiceTenant.fromJson(Map<String, dynamic> json) =>
_$ServiceTenantFromJson(json);
Map<String, dynamic> toJson() => _$ServiceTenantToJson(this);
}
@@ -72,3 +72,21 @@ Map<String, dynamic> _$ServiceAsyncFaceToJson(ServiceAsyncFace instance) =>
'deptId': instance.deptId,
'deptName': instance.deptName,
};
ServiceTenant _$ServiceTenantFromJson(Map<String, dynamic> json) =>
ServiceTenant(
id: json['id'] as String?,
code: json['code'] as String?,
name: json['name'] as String?,
menuId: json['menuId'] as String?,
miniQr: json['miniQr'] as String?,
);
Map<String, dynamic> _$ServiceTenantToJson(ServiceTenant instance) =>
<String, dynamic>{
'id': instance.id,
'code': instance.code,
'name': instance.name,
'menuId': instance.menuId,
'miniQr': instance.miniQr,
};
@@ -180,6 +180,9 @@ class UserSetting extends Equatable {
/// end 服务端信息 end
/// 租户id
String? tenantId;
UserSetting({
this.username,
this.password,
@@ -199,6 +202,7 @@ class UserSetting extends Equatable {
accessToken,
refreshToken,
expiresTime,
tenantId,
];
}
@@ -136,7 +136,8 @@ UserSetting _$UserSettingFromJson(Map<String, dynamic> json) => UserSetting(
..userId = (json['userId'] as num?)?.toInt()
..accessToken = json['accessToken'] as String?
..refreshToken = json['refreshToken'] as String?
..expiresTime = (json['expiresTime'] as num?)?.toInt();
..expiresTime = (json['expiresTime'] as num?)?.toInt()
..tenantId = json['tenantId'] as String?;
Map<String, dynamic> _$UserSettingToJson(UserSetting instance) =>
<String, dynamic>{
@@ -147,6 +148,7 @@ Map<String, dynamic> _$UserSettingToJson(UserSetting instance) =>
'accessToken': instance.accessToken,
'refreshToken': instance.refreshToken,
'expiresTime': instance.expiresTime,
'tenantId': instance.tenantId,
};
CanteenExtraPriceSetting _$CanteenExtraPriceSettingFromJson(
@@ -125,6 +125,10 @@ abstract class AppSettingTool {
static Future<void> setUserSetting(UserSetting userInfo) async {
final globalUserInfo = await getUserSetting();
if (globalUserInfo == userInfo) return;
if (ObjectUtil.isEmpty(userInfo.tenantId) &&
ObjectUtil.isNotEmpty(globalUserInfo.tenantId)) {
userInfo.tenantId = globalUserInfo.tenantId;
}
await _setSetting(userJson: userInfo);
}
+2 -2
View File
@@ -30,14 +30,14 @@ class HttpManage {
InterceptorsWrapper(
onRequest: (RequestOptions options, handler) async {
options.headers["CLIENT-TOC"] = "Y";
options.headers["TENANT-ID"] = 1;
final userInfo = await AppSettingTool.getUserSetting();
if (!options.path.contains(Constants.oauth2AppApiUrl)) {
final userInfo = await AppSettingTool.getUserSetting();
if (ObjectUtil.isNotEmpty(userInfo.accessToken)) {
options.headers['Authorization'] =
"Bearer ${userInfo.accessToken}";
}
}
options.headers["TENANT-ID"] = userInfo.tenantId ?? 1;
return handler.next(options);
},
onResponse: (e, handler) async {
+1 -1
View File
@@ -12,7 +12,7 @@ class Constants {
/// 服务 url
// static const apiUrl = 'https://stgl.pj.gov.cn/api';
static const apiUrl = 'http://192.168.101.81:9999';
static const apiUrl = 'http://192.168.101.107:9999';
/// 静态资源 url
static const ossUrl = apiUrl;
+1
View File
@@ -29,3 +29,4 @@ export './order/order_supermarket_common.dart';
export './setting/setting_password_dialog.dart';
export './user/user_info.dart';
export './user/user_tenant_dialog.dart';
@@ -0,0 +1,50 @@
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:forui/forui.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
Widget appTenantDialog(
BuildContext context,
FRadioSelectGroupController<String> controller, {
required List<ServiceTenant> list,
}) {
final theme = FTheme.of(context);
final selectList = list.where((v) => ObjectUtil.isNotEmpty(v.code));
return FSelectMenuTile<String>(
title: Text(
"单位",
style: theme.typography.base,
),
groupController: controller,
validator: (value) => value == null ? '请选择' : null,
autoHide: true,
prefixIcon: FIcon(
FAssets.icons.building2,
size: theme.typography.xl.fontSize,
),
menu: selectList.map((v) {
return FSelectTile(
title: Text(
v.name!,
style: theme.typography.base,
),
value: v.code!,
);
}).toList(),
details: ListenableBuilder(
listenable: controller,
builder: (context, _) {
final v = controller.value.firstOrNull;
if (v == null) return const SizedBox();
final target = selectList.firstWhere((i) => i.code == v);
return Text(
target.name!,
style: theme.typography.base.copyWith(
color: theme.colorScheme.primary,
),
);
},
),
);
}
+41 -2
View File
@@ -1,6 +1,7 @@
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:forui/forui.dart';
import 'package:get/get.dart';
@@ -11,8 +12,16 @@ class LoginController extends GetxController {
late SysSetting sysSetting;
late UserSetting userSetting;
Rx<SysType> sysType = SysType.CANTEEN.obs;
RxList<ServiceTenant> tenantList = <ServiceTenant>[].obs;
final tenantC = FRadioSelectGroupController<String>();
ServiceTenant currentTenant = ServiceTenant();
// 用户名
final usernameC = TextEditingController();
// 密码
@@ -26,8 +35,22 @@ class LoginController extends GetxController {
void onInit() async {
super.onInit();
sysSetting = await AppSettingTool.getSysSetting();
userSetting = await AppSettingTool.getUserSetting();
sysType.value = sysSetting.sysType;
await getTenantList();
await autoLogin();
tenantC.addValueListener((v) async {
final target = tenantList.firstWhereOrNull((i) => i.code == v.first);
if (target == null) return;
EasyLoading.show(
status: '切换中...',
maskType: EasyLoadingMaskType.black,
);
currentTenant = target;
userSetting.tenantId = target.id;
await AppSettingTool.setUserSetting(userSetting);
EasyLoading.dismiss();
});
}
@override
@@ -59,7 +82,6 @@ class LoginController extends GetxController {
if (visibleMsg) EasyLoading.dismiss();
return;
}
final userSetting = await AppSettingTool.getUserSetting();
userSetting.userId = data['user_id'];
userSetting.accessToken = data['access_token'];
userSetting.refreshToken = data['refresh_token'];
@@ -67,6 +89,7 @@ class LoginController extends GetxController {
userSetting.sysType = sysSetting.sysType;
userSetting.username = usernameC.value.text;
userSetting.password = passwordC.value.text;
userSetting.tenantId = currentTenant.id;
await AppSettingTool.setUserSetting(userSetting);
if (visibleMsg) {
Toast.success(msg: "登录成功");
@@ -82,7 +105,6 @@ class LoginController extends GetxController {
}
Future<void> autoLogin() async {
final userSetting = await AppSettingTool.getUserSetting();
if (ObjectUtil.isEmpty(userSetting.username) ||
ObjectUtil.isEmpty(userSetting.password) ||
ObjectUtil.isEmpty(userSetting.sysType?.name)) {
@@ -122,6 +144,23 @@ class LoginController extends GetxController {
return true;
}
Future<void> getTenantList() async {
try {
final list = await UserApi.getTenantList();
if (list == null) return;
tenantList.value = list;
if (ObjectUtil.isNotEmpty(userSetting.tenantId)) {
tenantC.value = {list.first.code!};
} else {
if (!ObjectUtil.isEmptyList(list)) {
tenantC.value = {list.first.code!};
}
}
} catch (e) {
logger.e("|$_tag|获取租户列表错误error:|$e|");
}
}
void _clearForm() {
usernameC.clear();
passwordC.clear();
+10 -1
View File
@@ -13,14 +13,23 @@ class LoginPage extends GetView<LoginController> {
// 主视图
Widget _buildView() {
final theme = FTheme.of(Get.context!);
final context = Get.context!;
return SizedBox(
width: Get.context!.sizes.width * 0.3,
width: context.sizes.width * 0.3,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FCard(
title: appLogo(),
child: [
SizedBox(height: 30.h),
Obx(
() => appTenantDialog(
context,
controller.tenantC,
list: controller.tenantList,
),
),
SizedBox(height: 30.h),
Obx(
() => FTextField(
@@ -21,7 +21,9 @@ Future<void> _bindServiceDialog(BuildContext context, SysSetting sys) async {
// 正式环境-测试
// uniqueCodeC.text = "1896157295523049473";
// 测试环境-测试设备
uniqueCodeC.text = "1877592554489143298";
// uniqueCodeC.text = "1877592554489143298";
// 测试环境-食堂-浦阳
uniqueCodeC.text = "1914567628657778690";
}
await showAdaptiveDialog(
context: context,