Flutter租车应用Firestore请求式写入:审批/拒绝/修改功能实现咨询
实现方案:基于Bloc+Repository的租车应用车辆审核流程
一、Firestore数据结构调整
先重构Firestore集合,拆分出审核请求和正式车辆数据两个核心集合:
car_submissions:存储车主提交的待审核请求,核心字段:carInfo:完整车辆信息(品牌、型号、牌照、租金等,和原车辆字段一致)ownerId:提交请求的车主UIDstatus:审核状态(pending/approved/rejected)adminNotes:管理员审核备注(审批/拒绝时填写)submittedAt:服务器生成的提交时间戳
cars:仅存储审核通过的正式车辆数据,保留原车辆字段,新增submissionId关联对应的审核请求
二、Repository层实现
1. 定义抽象Repository接口
统一数据操作入口,隔离业务逻辑与数据源:
abstract class CarSubmissionRepository { // 车主提交车辆审核请求 Future<void> submitCarSubmission(Map<String, dynamic> carInfo, String ownerId); // 获取当前车主的所有提交记录 Stream<List<Map<String, dynamic>>> getOwnerSubmissions(String ownerId); // 获取管理员待处理的审核请求列表 Stream<List<Map<String, dynamic>>> getPendingSubmissions(); // 管理员更新审核状态(审批/拒绝) Future<void> updateSubmissionStatus(String submissionId, String status, String? adminNotes); // 管理员修改提交的车辆信息 Future<void> updateSubmissionCarInfo(String submissionId, Map<String, dynamic> updatedCarInfo); }
2. Firestore具体实现类
class FirestoreCarSubmissionRepository implements CarSubmissionRepository { final FirebaseFirestore _firestore = FirebaseFirestore.instance; @override Future<void> submitCarSubmission(Map<String, dynamic> carInfo, String ownerId) async { await _firestore.collection('car_submissions').add({ 'carInfo': carInfo, 'ownerId': ownerId, 'status': 'pending', 'adminNotes': '', 'submittedAt': FieldValue.serverTimestamp(), }); } @override Stream<List<Map<String, dynamic>>> getOwnerSubmissions(String ownerId) { return _firestore .collection('car_submissions') .where('ownerId', isEqualTo: ownerId) .snapshots() .map((snapshot) => snapshot.docs.map((doc) => doc.data()).toList()); } @override Stream<List<Map<String, dynamic>>> getPendingSubmissions() { return _firestore .collection('car_submissions') .where('status', isEqualTo: 'pending') .snapshots() .map((snapshot) => snapshot.docs.map((doc) => doc.data()).toList()); } @override Future<void> updateSubmissionStatus(String submissionId, String status, String? adminNotes) async { final docRef = _firestore.collection('car_submissions').doc(submissionId); await docRef.update({ 'status': status, 'adminNotes': adminNotes ?? '', }); // 审批通过时同步车辆数据到正式集合 if (status == 'approved') { final submission = await docRef.get(); await _firestore.collection('cars').add({ ...submission.data()!['carInfo'], 'ownerId': submission.data()!['ownerId'], 'submissionId': submissionId, }); } } @override Future<void> updateSubmissionCarInfo(String submissionId, Map<String, dynamic> updatedCarInfo) async { await _firestore.collection('car_submissions').doc(submissionId).update({ 'carInfo': updatedCarInfo, }); } }
三、Bloc层实现
拆分车主侧和管理员侧两个独立Bloc,分离不同角色的业务逻辑
1. 车主侧CarSubmissionBloc
事件定义
abstract class CarSubmissionEvent {} class SubmitCarSubmission extends CarSubmissionEvent { final Map<String, dynamic> carInfo; final String ownerId; SubmitCarSubmission(this.carInfo, this.ownerId); } class FetchOwnerSubmissions extends CarSubmissionEvent { final String ownerId; FetchOwnerSubmissions(this.ownerId); }
状态定义
abstract class CarSubmissionState {} class CarSubmissionInitial extends CarSubmissionState {} class CarSubmissionLoading extends CarSubmissionState {} class CarSubmissionSuccess extends CarSubmissionState {} class CarSubmissionError extends CarSubmissionState { final String message; CarSubmissionError(this.message); } class OwnerSubmissionsLoaded extends CarSubmissionState { final List<Map<String, dynamic>> submissions; OwnerSubmissionsLoaded(this.submissions); }
Bloc核心逻辑
class CarSubmissionBloc extends Bloc<CarSubmissionEvent, CarSubmissionState> { final CarSubmissionRepository repository; CarSubmissionBloc(this.repository) : super(CarSubmissionInitial()) { on<SubmitCarSubmission>(_onSubmitCarSubmission); on<FetchOwnerSubmissions>(_onFetchOwnerSubmissions); } void _onSubmitCarSubmission(SubmitCarSubmission event, Emitter<CarSubmissionState> emit) async { emit(CarSubmissionLoading()); try { await repository.submitCarSubmission(event.carInfo, event.ownerId); emit(CarSubmissionSuccess()); } catch (e) { emit(CarSubmissionError(e.toString())); } } void _onFetchOwnerSubmissions(FetchOwnerSubmissions event, Emitter<CarSubmissionState> emit) async { emit(CarSubmissionLoading()); try { repository.getOwnerSubmissions(event.ownerId).listen((submissions) { emit(OwnerSubmissionsLoaded(submissions)); }); } catch (e) { emit(CarSubmissionError(e.toString())); } } }
2. 管理员侧AdminSubmissionBloc
事件定义
abstract class AdminSubmissionEvent {} class FetchPendingSubmissions extends AdminSubmissionEvent {} class ApproveSubmission extends AdminSubmissionEvent { final String submissionId; final String? adminNotes; ApproveSubmission(this.submissionId, this.adminNotes); } class RejectSubmission extends AdminSubmissionEvent { final String submissionId; final String adminNotes; RejectSubmission(this.submissionId, this.adminNotes); } class EditSubmissionCarInfo extends AdminSubmissionEvent { final String submissionId; final Map<String, dynamic> updatedCarInfo; EditSubmissionCarInfo(this.submissionId, this.updatedCarInfo); }
状态定义
abstract class AdminSubmissionState {} class AdminSubmissionInitial extends AdminSubmissionState {} class AdminSubmissionLoading extends AdminSubmissionState {} class PendingSubmissionsLoaded extends AdminSubmissionState { final List<Map<String, dynamic>> submissions; PendingSubmissionsLoaded(this.submissions); } class SubmissionActionSuccess extends AdminSubmissionState {} class AdminSubmissionError extends AdminSubmissionState { final String message; AdminSubmissionError(this.message); }
Bloc核心逻辑
class AdminSubmissionBloc extends Bloc<AdminSubmissionEvent, AdminSubmissionState> { final CarSubmissionRepository repository; AdminSubmissionBloc(this.repository) : super(AdminSubmissionInitial()) { on<FetchPendingSubmissions>(_onFetchPendingSubmissions); on<ApproveSubmission>(_onApproveSubmission); on<RejectSubmission>(_onRejectSubmission); on<EditSubmissionCarInfo>(_onEditSubmissionCarInfo); } void _onFetchPendingSubmissions(FetchPendingSubmissions event, Emitter<AdminSubmissionState> emit) async { emit(AdminSubmissionLoading()); try { repository.getPendingSubmissions().listen((submissions) { emit(PendingSubmissionsLoaded(submissions)); }); } catch (e) { emit(AdminSubmissionError(e.toString())); } } void _onApproveSubmission(ApproveSubmission event, Emitter<AdminSubmissionState> emit) async { emit(AdminSubmissionLoading()); try { await repository.updateSubmissionStatus(event.submissionId, 'approved', event.adminNotes); emit(SubmissionActionSuccess()); } catch (e) { emit(AdminSubmissionError(e.toString())); } } void _onRejectSubmission(RejectSubmission event, Emitter<AdminSubmissionState> emit) async { emit(AdminSubmissionLoading()); try { await repository.updateSubmissionStatus(event.submissionId, 'rejected', event.adminNotes); emit(SubmissionActionSuccess()); } catch (e) { emit(AdminSubmissionError(e.toString())); } } void _onEditSubmissionCarInfo(EditSubmissionCarInfo event, Emitter<AdminSubmissionState> emit) async { emit(AdminSubmissionLoading()); try { await repository.updateSubmissionCarInfo(event.submissionId, event.updatedCarInfo); emit(SubmissionActionSuccess()); } catch (e) { emit(AdminSubmissionError(e.toString())); } } }
四、UI层集成
车主侧UI
- 车辆提交页面:用户填写信息后触发
SubmitCarSubmission事件,根据Bloc状态展示加载、成功提示或错误信息 - 提交记录页面:触发
FetchOwnerSubmissions事件,展示所有提交的状态(待审核/通过/拒绝)及管理员备注
管理员侧UI
- 待审核列表页面:触发
FetchPendingSubmissions事件,展示所有待审核请求,每条请求提供编辑、审批、拒绝操作按钮 - 车辆信息编辑弹窗:修改完成后触发
EditSubmissionCarInfo事件 - 审批/拒绝操作:点击对应按钮触发
ApproveSubmission或RejectSubmission事件,支持输入审核备注
五、额外优化点
- 状态通知:结合Firebase Cloud Messaging,当审核状态变更时给车主推送通知
- 权限控制:通过Firebase Auth自定义声明(
setCustomUserClaims)判断用户角色,确保仅管理员能访问审核页面 - 数据校验:在Repository层添加车辆信息合法性校验,拦截无效提交
内容的提问来源于stack exchange,提问作者xs0mE1
相关产品推荐
相关产品推荐

