Commit 48dff085 by tanghuan

注销用户相关整改

1 parent 8081bf8e
...@@ -79,6 +79,22 @@ class AccountCubit extends Cubit<AccountState> { ...@@ -79,6 +79,22 @@ class AccountCubit extends Cubit<AccountState> {
} }
} }
Future<void> goSetUserInfo() async {
dynamic result = await router.push(
'/account/user',
extra: {
'name': state.name,
'nickname': state.nickname,
'avatar': state.imgIcon,
},
);
if (result != null && result.isNotEmpty) {
Map<String, dynamic> resultMap = Map<String, dynamic>.from(result);
emit(state.copyWith(imgIcon: resultMap['avatar'], name: resultMap['name'], nickname: resultMap['nickname']));
}
}
Future<void> goBind() async { Future<void> goBind() async {
String? result = await router.push( String? result = await router.push(
'/account/phone', '/account/phone',
...@@ -91,15 +107,35 @@ class AccountCubit extends Cubit<AccountState> { ...@@ -91,15 +107,35 @@ class AccountCubit extends Cubit<AccountState> {
} }
} }
Future<void> unbind() async { void goLogoff() {
router.push(
'/account/logoff',
extra: {
'phone': state.phone,
},
);
}
var sharedPreferences = getIt.get<SharedPreferences>(); Future<void> unbind() async {
var userCode = sharedPreferences.getString('auth_userCode') ?? ''; // var sharedPreferences = getIt.get<SharedPreferences>();
_phoneAuthRepository.unbind(userCode); // var userCode = sharedPreferences.getString('auth_userCode') ?? '';
// _phoneAuthRepository.unbind(userCode);
// 当前只会成功,不会失败 // 当前只会成功,不会失败
// 解绑成功,跳转登录界面 // 解绑成功,跳转登录界面
// router.go('/loginMain');
// emit(state.copyWith(showSnackBar: true, snackBarMsg: '操作成功'));
// emit(state.copyWith(showSnackBar: false));
// await Future.delayed(Duration(seconds: 1));
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.getKeys().forEach((key) {
if (key.startsWith('auth_')) {
sharedPreferences.remove(key);
}
});
router.go('/loginMain'); router.go('/loginMain');
} }
} }
import 'dart:async';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AccountLogoffState extends Equatable {
final String phone;
final bool showSnackBar;
final String snackBarMsg;
final bool allowSend;
final int seconds;
final bool isLoading;
const AccountLogoffState({
this.phone = '',
this.showSnackBar = false,
this.snackBarMsg = '',
this.allowSend = true,
this.seconds = 0,
this.isLoading = false,
});
AccountLogoffState copyWith({
String? phone,
bool? showSnackBar,
String? snackBarMsg,
bool? allowSend,
int? seconds,
bool? isLoading,
}) {
return AccountLogoffState(
phone: phone ?? this.phone,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
allowSend: allowSend ?? this.allowSend,
seconds: seconds ?? this.seconds,
isLoading: isLoading ?? this.isLoading,
);
}
@override
List<Object?> get props => [
phone,
showSnackBar,
snackBarMsg,
allowSend,
seconds,
isLoading,
];
}
class AccountLogoffCubit extends Cubit<AccountLogoffState> {
late TextEditingController _codeController;
Timer? _timer;
int countdown = 60;
late final PhoneAuthRepository _phoneAuthRepository;
TextEditingController get codeController => _codeController;
AccountLogoffCubit(super.initialState) {
_codeController = TextEditingController();
_codeController.text = '';
_phoneAuthRepository = getIt.get<PhoneAuthRepository>();
}
/// 开始倒计时
void startCountdown() {
countdown = 60;
emit(state.copyWith(allowSend: false, seconds: countdown));
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
countdown--;
if (countdown <= 0) {
_timer?.cancel();
emit(state.copyWith(allowSend: true, seconds: 60));
} else {
emit(state.copyWith(seconds: countdown));
}
});
}
/// 发送验证码
Future<void> sendVerificationCode() async {
if (state.allowSend) {
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(state.phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '手机号码信息错误'));
emit(state.copyWith(showSnackBar: false));
return;
}
var result = await _phoneAuthRepository.verifyCode(state.phone, 1);
if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error']));
emit(state.copyWith(showSnackBar: false));
return;
}
emit(state.copyWith(allowSend: false, seconds: 60));
startCountdown();
}
}
/// 注销账户
Future<void> logoff() async {
// String verifyCode = _codeController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(state.phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '手机号码信息错误'));
emit(state.copyWith(showSnackBar: false));
return;
}
// if (!RegExp(r'^\d{4}$').hasMatch(verifyCode)) {
// emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的验证码'));
// emit(state.copyWith(showSnackBar: false));
// return;
// }
emit(state.copyWith(isLoading: true));
// var sharedPreferences = getIt.get<SharedPreferences>();
// var userCode = sharedPreferences.getString('auth_userCode') ?? '';
// var result = await _phoneAuthRepository.unbindWithVerifyCode(userCode, verifyCode);
var result = await _phoneAuthRepository.unbindPhone(state.phone);
emit(state.copyWith(isLoading: false));
if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error']));
emit(state.copyWith(showSnackBar: false));
return;
}
emit(state.copyWith(showSnackBar: true, snackBarMsg: '操作成功'));
emit(state.copyWith(showSnackBar: false));
await Future.delayed(Duration(seconds: 1));
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.getKeys().forEach((key) {
if (key.startsWith('auth_')) {
sharedPreferences.remove(key);
}
});
router.go('/loginMain');
}
@override
Future<void> close() async {
_timer?.cancel();
try {
_codeController.dispose();
} catch (e) {
print(e);
}
await super.close();
}
}
...@@ -53,7 +53,7 @@ class Constant { ...@@ -53,7 +53,7 @@ class Constant {
static const String appVersion = EnvConfig.version; static const String appVersion = EnvConfig.version;
/// H5的起始终最低版本号规则 /// H5的起始终最低版本号规则
static String h5Version = '0.0.0'; static String h5Version = '0.1.6';
/// H5的版本号存储的key /// H5的版本号存储的key
static const String h5VersionKey = 'h5_version'; static const String h5VersionKey = 'h5_version';
......
...@@ -9,8 +9,10 @@ import 'package:appframe/ui/pages/login_phone_page.dart'; ...@@ -9,8 +9,10 @@ import 'package:appframe/ui/pages/login_phone_page.dart';
import 'package:appframe/ui/pages/login_qr_page.dart'; import 'package:appframe/ui/pages/login_qr_page.dart';
import 'package:appframe/ui/pages/reload_page.dart'; import 'package:appframe/ui/pages/reload_page.dart';
import 'package:appframe/ui/pages/scan_code_page.dart'; import 'package:appframe/ui/pages/scan_code_page.dart';
import 'package:appframe/ui/pages/setting/account_logoff_page.dart';
import 'package:appframe/ui/pages/setting/account_page.dart'; import 'package:appframe/ui/pages/setting/account_page.dart';
import 'package:appframe/ui/pages/setting/account_phone_page.dart'; import 'package:appframe/ui/pages/setting/account_phone_page.dart';
import 'package:appframe/ui/pages/setting/account_user_page.dart';
import 'package:appframe/ui/pages/web_page.dart'; import 'package:appframe/ui/pages/web_page.dart';
import 'package:appframe/ui/widgets/ios_edge_swipe_detector.dart'; import 'package:appframe/ui/widgets/ios_edge_swipe_detector.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
...@@ -69,6 +71,18 @@ final GoRouter router = GoRouter( ...@@ -69,6 +71,18 @@ final GoRouter router = GoRouter(
}, },
), ),
GoRoute( GoRoute(
path: '/account/user',
builder: (BuildContext context, GoRouterState state) {
return const AccountUserPage();
},
),
GoRoute(
path: '/account/logoff',
builder: (BuildContext context, GoRouterState state) {
return const AccountLogoffPage();
},
),
GoRoute(
path: '/adv', path: '/adv',
builder: (BuildContext context, GoRouterState state) { builder: (BuildContext context, GoRouterState state) {
return const AdvPage(); return const AdvPage();
......
...@@ -102,4 +102,33 @@ class PhoneAuthRepository { ...@@ -102,4 +102,33 @@ class PhoneAuthRepository {
); );
return resp.data; return resp.data;
} }
Future<dynamic> unbindPhone(String phone) async {
Response resp = await _appService.post(
'/api/v1/comm/phone/unbind',
{
"phone": phone,
},
);
return resp.data;
}
///
/// {
/// "code": 0,
/// "error": "操作成功"
/// }
///
Future<dynamic> updateUser(String userid, String name, String nickName, String avatar) async {
Response resp = await _appService.post(
'/api/v1/comm/user/update',
{
"userid": userid,
"name": name,
"nickName": nickName,
"avatar": avatar,
},
);
return resp.data;
}
} }
import 'package:appframe/bloc/setting/account_logoff_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class AccountLogoffPage extends StatelessWidget {
const AccountLogoffPage({super.key});
@override
Widget build(BuildContext context) {
final Map<String, dynamic>? extraData = GoRouterState.of(context).extra as Map<String, dynamic>?;
var phone = extraData?['phone'] ?? '';
return BlocProvider(
create: (context) => AccountLogoffCubit(AccountLogoffState(phone: phone)),
child: BlocConsumer<AccountLogoffCubit, AccountLogoffState>(
builder: (context, state) {
final accountLogoffCubit = context.read<AccountLogoffCubit>();
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('注销用户', style: TextStyle(color: Colors.white, fontSize: 18)),
centerTitle: true,
backgroundColor: Color(0xFF7691FA),
iconTheme: IconThemeData(
color: Colors.white,
),
),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(60),
child: Column(
children: [
SizedBox(height: 60),
Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Color(0xFFFFF3F3),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Color(0xFFFFCDD2)),
),
child: Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: Color(0xFFE74C3C),
size: 24,
),
SizedBox(width: 12),
Expanded(
child: Text(
'注销后,您的所有数据将被永久删除且无法恢复,请谨慎操作!',
style: TextStyle(
fontSize: 14,
color: Color(0xFFE74C3C),
height: 1.5,
),
),
),
],
),
),
SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 47,
child: ElevatedButton(
onPressed: state.isLoading
? null
: () {
accountLogoffCubit.logoff();
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFFE74C3C),
foregroundColor: Colors.white,
textStyle: TextStyle(fontSize: 19),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(23.5),
),
disabledBackgroundColor: Color(0xFFCCCCCC),
),
child: state.isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
'确认注销',
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.w400,
color: Color(0xFFFFFFFF),
),
strutStyle: StrutStyle(height: 22 / 19),
),
),
),
],
),
),
),
);
},
listener: (context, state) {
if (state.showSnackBar) {
_showTip(context, state.snackBarMsg);
}
},
),
);
}
void _showTip(BuildContext context, String tip) {
OverlayEntry overlayEntry = OverlayEntry(
builder: (context) => Positioned(
top: 200,
left: 20,
right: 20,
child: Material(
color: Colors.transparent,
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(8),
),
child: Text(
tip,
style: TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
),
),
),
);
Overlay.of(context).insert(overlayEntry);
Future.delayed(Duration(seconds: 2), () {
overlayEntry.remove();
});
}
}
...@@ -60,6 +60,27 @@ class AccountPage extends StatelessWidget { ...@@ -60,6 +60,27 @@ class AccountPage extends StatelessWidget {
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: ListTile( child: ListTile(
leading: Icon(Icons.person),
title: Text('用户信息设置'),
subtitle: Text(
'点击设置用户信息',
style: TextStyle(
fontSize: 14.0,
color: Colors.grey,
),
),
trailing: Icon(Icons.arrow_forward_ios, size: 14),
onTap: () {
context.read<AccountCubit>().goSetUserInfo();
},
),
),
Card(
color: Color(0xFFF7F9FF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: ListTile(
leading: Icon(Icons.mobile_friendly), leading: Icon(Icons.mobile_friendly),
title: Text('手机号绑定'), title: Text('手机号绑定'),
subtitle: Text( subtitle: Text(
...@@ -85,6 +106,12 @@ class AccountPage extends StatelessWidget { ...@@ -85,6 +106,12 @@ class AccountPage extends StatelessWidget {
height: 47, height: 47,
child: ElevatedButton( child: ElevatedButton(
onPressed: () async { onPressed: () async {
// 判断是否有绑定手机号
if (state.phone != '') {
context.read<AccountCubit>().goLogoff();
return;
}
final accountCubit = context.read<AccountCubit>(); final accountCubit = context.read<AccountCubit>();
bool? confirm = await showDialog<bool>( bool? confirm = await showDialog<bool>(
context: context, context: context,
...@@ -111,11 +138,17 @@ class AccountPage extends StatelessWidget { ...@@ -111,11 +138,17 @@ class AccountPage extends StatelessWidget {
); );
if (confirm == true) { if (confirm == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已申请注销,等待流程处理'),
backgroundColor: Colors.green,
),
);
accountCubit.unbind(); accountCubit.unbind();
} }
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF7691FA), backgroundColor: Color(0xFFE74C3C),
foregroundColor: Colors.white, foregroundColor: Colors.white,
textStyle: TextStyle(fontSize: 19), textStyle: TextStyle(fontSize: 19),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
......
...@@ -316,25 +316,21 @@ class WebPage extends StatelessWidget { ...@@ -316,25 +316,21 @@ class WebPage extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
ListTile( ListTile(
leading: const Icon(Icons.cleaning_services, size: 20), leading: const Icon(Icons.headset_mic, size: 20),
title: const Text('清理缓存', style: TextStyle(fontSize: 14)), title: const Text('在线客服', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
_showClearCacheDialog(ctx);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
dense: true,
visualDensity: VisualDensity.compact,
),
Divider(height: 1, thickness: 0.5, indent: 16, endIndent: 16),
ListTile(
leading: const Icon(Icons.logout, size: 20),
title: const Text('退出登录', style: TextStyle(fontSize: 14)),
onTap: () { onTap: () {
Navigator.pop(ctx); Navigator.pop(ctx);
ctx.read<WebCubit>().logout(); router.push(
'/link',
extra: {
'url':
'https://yuanqi.tencent.com/webim/#/chat/DKfyFo?appid=1970738784338535872&experience=true',
'title': '在线客服'
},
);
}, },
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true, dense: true,
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
), ),
...@@ -350,20 +346,56 @@ class WebPage extends StatelessWidget { ...@@ -350,20 +346,56 @@ class WebPage extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
ListTile( ListTile(
leading: const Icon(Icons.timeline, size: 20), leading: const Icon(Icons.cleaning_services, size: 20),
title: const Text('切换日志模式', style: TextStyle(fontSize: 14)), title: const Text('清理缓存', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
_showClearCacheDialog(ctx);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
dense: true,
visualDensity: VisualDensity.compact,
),
Divider(height: 1, thickness: 0.5, indent: 16, endIndent: 16),
ListTile(
leading: const Icon(Icons.logout, size: 20),
title: const Text('退出登录', style: TextStyle(fontSize: 14)),
onTap: () { onTap: () {
Navigator.pop(ctx); Navigator.pop(ctx);
ctx.read<WebCubit>().handleToggleDebug(); ctx.read<WebCubit>().logout();
}, },
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
// trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true, dense: true,
visualDensity: VisualDensity.compact, // 视觉密度设为紧凑 visualDensity: VisualDensity.compact,
), ),
], ],
), ),
), ),
SizedBox(height: 8),
EnvConfig.isDev()
? Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.timeline, size: 20),
title: const Text('切换日志模式', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
ctx.read<WebCubit>().handleToggleDebug();
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
// trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact, // 视觉密度设为紧凑
),
],
),
)
: SizedBox(),
], ],
), ),
), ),
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!