Commit 48dff085 by tanghuan

注销用户相关整改

1 parent 8081bf8e
......@@ -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 {
String? result = await router.push(
'/account/phone',
......@@ -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>();
var userCode = sharedPreferences.getString('auth_userCode') ?? '';
_phoneAuthRepository.unbind(userCode);
Future<void> unbind() async {
// var sharedPreferences = getIt.get<SharedPreferences>();
// 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');
}
}
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 {
static const String appVersion = EnvConfig.version;
/// H5的起始终最低版本号规则
static String h5Version = '0.0.0';
static String h5Version = '0.1.6';
/// H5的版本号存储的key
static const String h5VersionKey = 'h5_version';
......
......@@ -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/reload_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_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/widgets/ios_edge_swipe_detector.dart';
import 'package:flutter/material.dart';
......@@ -69,6 +71,18 @@ final GoRouter router = GoRouter(
},
),
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',
builder: (BuildContext context, GoRouterState state) {
return const AdvPage();
......
......@@ -102,4 +102,33 @@ class PhoneAuthRepository {
);
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 {
borderRadius: BorderRadius.circular(6),
),
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),
title: Text('手机号绑定'),
subtitle: Text(
......@@ -85,6 +106,12 @@ class AccountPage extends StatelessWidget {
height: 47,
child: ElevatedButton(
onPressed: () async {
// 判断是否有绑定手机号
if (state.phone != '') {
context.read<AccountCubit>().goLogoff();
return;
}
final accountCubit = context.read<AccountCubit>();
bool? confirm = await showDialog<bool>(
context: context,
......@@ -111,11 +138,17 @@ class AccountPage extends StatelessWidget {
);
if (confirm == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已申请注销,等待流程处理'),
backgroundColor: Colors.green,
),
);
accountCubit.unbind();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF7691FA),
backgroundColor: Color(0xFFE74C3C),
foregroundColor: Colors.white,
textStyle: TextStyle(fontSize: 19),
shape: RoundedRectangleBorder(
......
......@@ -70,7 +70,8 @@ class AccountPhonePage extends StatelessWidget {
)),
]),
)
: Padding(
: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(60),
child: Column(
children: [
......@@ -221,6 +222,7 @@ class AccountPhonePage extends StatelessWidget {
],
),
),
),
);
},
listener: (context, state) {
......
......@@ -316,6 +316,36 @@ class WebPage extends StatelessWidget {
child: Column(
children: [
ListTile(
leading: const Icon(Icons.headset_mic, size: 20),
title: const Text('在线客服', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
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),
trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
],
),
),
SizedBox(height: 8),
Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.cleaning_services, size: 20),
title: const Text('清理缓存', style: TextStyle(fontSize: 14)),
onTap: () {
......@@ -342,7 +372,8 @@ class WebPage extends StatelessWidget {
),
),
SizedBox(height: 8),
Card(
EnvConfig.isDev()
? Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
......@@ -363,7 +394,8 @@ class WebPage extends StatelessWidget {
),
],
),
),
)
: SizedBox(),
],
),
),
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!