first commit
This commit is contained in:
131
after_sales/README.md
Normal file
131
after_sales/README.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# After Sales 售后服务模块群
|
||||
|
||||
## 模块群概述
|
||||
|
||||
After Sales 模块群是 OneApp 的售后服务业务模块集合,提供了完整的汽车售后服务功能。该模块群包含了维修预约、服务记录、技师派工、客户满意度调查等核心售后业务功能。
|
||||
|
||||
## 子模块列表
|
||||
|
||||
### 核心模块
|
||||
1. **[oneapp_after_sales](./oneapp_after_sales.md)** - 售后服务主模块
|
||||
- 维修预约和服务管理
|
||||
- 服务记录和历史查询
|
||||
- 客户端售后功能入口
|
||||
|
||||
2. **[clr_after_sales](./clr_after_sales.md)** - 售后服务SDK
|
||||
- 售后服务业务逻辑封装
|
||||
- API接口统一管理
|
||||
- 数据模型定义
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 核心业务功能
|
||||
1. **维修预约服务**
|
||||
- 在线预约维修时间
|
||||
- 服务门店选择
|
||||
- 预约状态跟踪
|
||||
- 预约变更和取消
|
||||
|
||||
2. **服务记录管理**
|
||||
- 维修历史记录
|
||||
- 服务项目详情
|
||||
- 费用明细查看
|
||||
- 服务评价反馈
|
||||
|
||||
3. **技师服务管理**
|
||||
- 技师信息展示
|
||||
- 服务进度跟踪
|
||||
- 实时沟通功能
|
||||
- 服务质量评估
|
||||
|
||||
4. **客户体验优化**
|
||||
- 服务满意度调查
|
||||
- 投诉建议处理
|
||||
- 会员权益服务
|
||||
- 增值服务推荐
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 模块架构图
|
||||
```
|
||||
售后服务应用层 (oneapp_after_sales)
|
||||
↓
|
||||
售后服务SDK (clr_after_sales)
|
||||
↓
|
||||
基础服务层 (basic_*)
|
||||
↓
|
||||
原生平台能力
|
||||
```
|
||||
|
||||
### 主要依赖
|
||||
- **地图服务**: ui_mapview, amap_flutter_location
|
||||
- **支付服务**: kit_alipay, fluwx
|
||||
- **图片处理**: photo_view, photo_gallery
|
||||
- **电子签名**: signature
|
||||
- **权限管理**: permission_handler
|
||||
- **网络通信**: dio
|
||||
- **WebView**: basic_webview
|
||||
|
||||
## 业务流程
|
||||
|
||||
### 维修预约流程
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户发起预约] --> B[选择服务类型]
|
||||
B --> C[选择服务门店]
|
||||
C --> D[选择预约时间]
|
||||
D --> E[填写车辆信息]
|
||||
E --> F[确认预约信息]
|
||||
F --> G[支付定金]
|
||||
G --> H[预约成功]
|
||||
H --> I[门店确认]
|
||||
I --> J[服务执行]
|
||||
J --> K[服务完成]
|
||||
K --> L[客户评价]
|
||||
```
|
||||
|
||||
### 服务执行流程
|
||||
```mermaid
|
||||
graph TD
|
||||
A[客户到店] --> B[接车检查]
|
||||
B --> C[故障诊断]
|
||||
C --> D[制定维修方案]
|
||||
D --> E[客户确认]
|
||||
E --> F[开始维修]
|
||||
F --> G[维修过程]
|
||||
G --> H[质量检验]
|
||||
H --> I[交车准备]
|
||||
I --> J[客户验收]
|
||||
J --> K[结算付款]
|
||||
K --> L[服务完成]
|
||||
```
|
||||
|
||||
## 详细模块文档
|
||||
|
||||
- [OneApp After Sales - 售后服务主模块](./oneapp_after_sales.md)
|
||||
- [CLR After Sales - 售后服务SDK](./clr_after_sales.md)
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 环境要求
|
||||
- Flutter >=1.17.0
|
||||
- Dart >=3.0.0 <4.0.0
|
||||
- Android SDK >=21
|
||||
- iOS >=11.0
|
||||
|
||||
### 快速开始
|
||||
```dart
|
||||
// 初始化售后服务模块
|
||||
await AfterSalesService.initialize();
|
||||
|
||||
// 创建维修预约
|
||||
final appointment = await AfterSalesService.createAppointment(
|
||||
serviceType: ServiceType.maintenance,
|
||||
storeId: 'store_123',
|
||||
appointmentTime: DateTime.now().add(Duration(days: 1)),
|
||||
);
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
After Sales 模块群为 OneApp 提供了完整的售后服务解决方案,通过标准化的业务流程和用户友好的界面设计,提升了售后服务的效率和客户满意度。模块群具有良好的扩展性,能够适应不同的售后服务场景和业务需求。
|
||||
726
after_sales/clr_after_sales.md
Normal file
726
after_sales/clr_after_sales.md
Normal file
@@ -0,0 +1,726 @@
|
||||
# CLR After Sales 售后服务SDK
|
||||
|
||||
## 模块概述
|
||||
|
||||
`clr_after_sales` 是 OneApp 售后服务模块群中的核心服务SDK,负责封装售后服务的业务逻辑、API接口调用和数据模型定义。该模块为售后服务应用层提供统一的服务接口和数据处理能力。
|
||||
|
||||
### 基本信息
|
||||
- **模块名称**: clr_after_sales
|
||||
- **版本**: 0.0.1
|
||||
- **描述**: 售后服务核心SDK
|
||||
- **Flutter 版本**: >=1.17.0
|
||||
- **Dart 版本**: >=3.0.0 <4.0.0
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 核心功能
|
||||
1. **API接口封装**
|
||||
- 售后服务API统一封装
|
||||
- 网络请求统一处理
|
||||
- 错误处理和重试机制
|
||||
- 数据缓存和同步
|
||||
|
||||
2. **业务逻辑封装**
|
||||
- 预约业务逻辑
|
||||
- 服务流程管理
|
||||
- 支付结算逻辑
|
||||
- 数据验证规则
|
||||
|
||||
3. **数据模型管理**
|
||||
- 统一数据模型定义
|
||||
- JSON序列化支持
|
||||
- 数据转换和映射
|
||||
- 模型验证机制
|
||||
|
||||
4. **状态管理**
|
||||
- 业务状态统一管理
|
||||
- 事件驱动更新
|
||||
- 状态持久化
|
||||
- 状态同步机制
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
lib/
|
||||
├── clr_after_sales.dart # 模块入口文件
|
||||
├── src/ # 源代码目录
|
||||
│ ├── services/ # 业务服务
|
||||
│ ├── repositories/ # 数据仓库
|
||||
│ ├── models/ # 数据模型
|
||||
│ ├── enums/ # 枚举定义
|
||||
│ ├── exceptions/ # 异常定义
|
||||
│ ├── utils/ # 工具类
|
||||
│ └── constants/ # 常量定义
|
||||
├── test/ # 测试文件
|
||||
└── generated/ # 代码生成文件
|
||||
```
|
||||
|
||||
### 依赖关系
|
||||
|
||||
#### 基础框架依赖
|
||||
- `basic_network: ^0.2.3+4` - 网络通信框架
|
||||
- `common_utils: ^2.1.0` - 通用工具库
|
||||
|
||||
#### 内部依赖 (dependency_overrides)
|
||||
- `ui_basic` - 基础UI组件(本地路径)
|
||||
- `basic_platform` - 平台适配(本地路径)
|
||||
- `basic_utils` - 基础工具(本地路径)
|
||||
- `basic_uis` - 基础UI集合(本地路径)
|
||||
- `base_mvvm` - MVVM架构(本地路径)
|
||||
|
||||
## 核心模块分析
|
||||
|
||||
### 1. 业务服务 (`src/services/`)
|
||||
|
||||
#### 预约服务
|
||||
```dart
|
||||
class AppointmentService {
|
||||
final AppointmentRepository _repository;
|
||||
final NetworkService _networkService;
|
||||
|
||||
AppointmentService(this._repository, this._networkService);
|
||||
|
||||
/// 创建预约
|
||||
Future<Result<Appointment>> createAppointment(
|
||||
AppointmentCreateRequest request,
|
||||
) async {
|
||||
try {
|
||||
// 1. 验证请求数据
|
||||
final validationResult = _validateCreateRequest(request);
|
||||
if (validationResult.isFailure) {
|
||||
return Result.failure(validationResult.error);
|
||||
}
|
||||
|
||||
// 2. 检查时间可用性
|
||||
final availability = await checkTimeAvailability(
|
||||
request.storeId,
|
||||
request.appointmentTime,
|
||||
);
|
||||
|
||||
if (!availability.isAvailable) {
|
||||
return Result.failure(
|
||||
AppointmentException('预约时间不可用'),
|
||||
);
|
||||
}
|
||||
|
||||
// 3. 调用API创建预约
|
||||
final apiResponse = await _networkService.post(
|
||||
'/appointments',
|
||||
data: request.toJson(),
|
||||
);
|
||||
|
||||
// 4. 解析响应数据
|
||||
final appointment = Appointment.fromJson(apiResponse.data);
|
||||
|
||||
// 5. 缓存到本地
|
||||
await _repository.saveAppointment(appointment);
|
||||
|
||||
return Result.success(appointment);
|
||||
} catch (e) {
|
||||
return Result.failure(AppointmentException(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取用户预约列表
|
||||
Future<Result<List<Appointment>>> getUserAppointments({
|
||||
String? status,
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
try {
|
||||
// 1. 先从缓存获取
|
||||
final cachedAppointments = await _repository.getCachedAppointments(
|
||||
status: status,
|
||||
page: page,
|
||||
pageSize: pageSize,
|
||||
);
|
||||
|
||||
// 2. 如果缓存存在且未过期,直接返回
|
||||
if (cachedAppointments.isNotEmpty && !_isCacheExpired()) {
|
||||
return Result.success(cachedAppointments);
|
||||
}
|
||||
|
||||
// 3. 从网络获取最新数据
|
||||
final apiResponse = await _networkService.get(
|
||||
'/appointments',
|
||||
queryParameters: {
|
||||
'status': status,
|
||||
'page': page,
|
||||
'pageSize': pageSize,
|
||||
},
|
||||
);
|
||||
|
||||
// 4. 解析并缓存数据
|
||||
final appointments = (apiResponse.data['items'] as List)
|
||||
.map((json) => Appointment.fromJson(json))
|
||||
.toList();
|
||||
|
||||
await _repository.cacheAppointments(appointments);
|
||||
|
||||
return Result.success(appointments);
|
||||
} catch (e) {
|
||||
return Result.failure(AppointmentException(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消预约
|
||||
Future<Result<bool>> cancelAppointment(String appointmentId) async {
|
||||
try {
|
||||
await _networkService.put(
|
||||
'/appointments/$appointmentId/cancel',
|
||||
);
|
||||
|
||||
// 更新本地缓存
|
||||
await _repository.updateAppointmentStatus(
|
||||
appointmentId,
|
||||
AppointmentStatus.cancelled,
|
||||
);
|
||||
|
||||
return Result.success(true);
|
||||
} catch (e) {
|
||||
return Result.failure(AppointmentException(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 服务进度服务
|
||||
```dart
|
||||
class ServiceProgressService {
|
||||
final ServiceProgressRepository _repository;
|
||||
final NetworkService _networkService;
|
||||
|
||||
ServiceProgressService(this._repository, this._networkService);
|
||||
|
||||
/// 获取服务进度
|
||||
Future<Result<ServiceProgress>> getServiceProgress(
|
||||
String appointmentId,
|
||||
) async {
|
||||
try {
|
||||
final apiResponse = await _networkService.get(
|
||||
'/appointments/$appointmentId/progress',
|
||||
);
|
||||
|
||||
final progress = ServiceProgress.fromJson(apiResponse.data);
|
||||
|
||||
// 缓存进度数据
|
||||
await _repository.saveProgress(progress);
|
||||
|
||||
return Result.success(progress);
|
||||
} catch (e) {
|
||||
return Result.failure(ServiceException(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新服务步骤
|
||||
Future<Result<ServiceStep>> updateServiceStep(
|
||||
String appointmentId,
|
||||
String stepId,
|
||||
ServiceStepUpdate update,
|
||||
) async {
|
||||
try {
|
||||
final apiResponse = await _networkService.put(
|
||||
'/appointments/$appointmentId/steps/$stepId',
|
||||
data: update.toJson(),
|
||||
);
|
||||
|
||||
final updatedStep = ServiceStep.fromJson(apiResponse.data);
|
||||
|
||||
// 更新本地缓存
|
||||
await _repository.updateStep(appointmentId, updatedStep);
|
||||
|
||||
return Result.success(updatedStep);
|
||||
} catch (e) {
|
||||
return Result.failure(ServiceException(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
/// 上传服务照片
|
||||
Future<Result<ServicePhoto>> uploadServicePhoto(
|
||||
String appointmentId,
|
||||
String stepId,
|
||||
File photoFile,
|
||||
) async {
|
||||
try {
|
||||
final formData = FormData.fromMap({
|
||||
'file': await MultipartFile.fromFile(photoFile.path),
|
||||
'appointmentId': appointmentId,
|
||||
'stepId': stepId,
|
||||
});
|
||||
|
||||
final apiResponse = await _networkService.post(
|
||||
'/service-photos/upload',
|
||||
data: formData,
|
||||
);
|
||||
|
||||
final photo = ServicePhoto.fromJson(apiResponse.data);
|
||||
|
||||
return Result.success(photo);
|
||||
} catch (e) {
|
||||
return Result.failure(ServiceException(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 数据仓库 (`src/repositories/`)
|
||||
|
||||
#### 预约数据仓库
|
||||
```dart
|
||||
abstract class AppointmentRepository {
|
||||
Future<void> saveAppointment(Appointment appointment);
|
||||
Future<List<Appointment>> getCachedAppointments({
|
||||
String? status,
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
});
|
||||
Future<void> cacheAppointments(List<Appointment> appointments);
|
||||
Future<void> updateAppointmentStatus(String id, AppointmentStatus status);
|
||||
Future<void> clearCache();
|
||||
}
|
||||
|
||||
class AppointmentRepositoryImpl implements AppointmentRepository {
|
||||
final LocalStorage _localStorage;
|
||||
final DatabaseHelper _databaseHelper;
|
||||
|
||||
AppointmentRepositoryImpl(this._localStorage, this._databaseHelper);
|
||||
|
||||
@override
|
||||
Future<void> saveAppointment(Appointment appointment) async {
|
||||
try {
|
||||
// 保存到数据库
|
||||
await _databaseHelper.insertAppointment(appointment);
|
||||
|
||||
// 更新缓存
|
||||
final cacheKey = 'appointment_${appointment.id}';
|
||||
await _localStorage.setString(cacheKey, jsonEncode(appointment.toJson()));
|
||||
} catch (e) {
|
||||
throw RepositoryException('保存预约失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Appointment>> getCachedAppointments({
|
||||
String? status,
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
try {
|
||||
return await _databaseHelper.getAppointments(
|
||||
status: status,
|
||||
offset: (page - 1) * pageSize,
|
||||
limit: pageSize,
|
||||
);
|
||||
} catch (e) {
|
||||
throw RepositoryException('获取缓存预约失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAppointmentStatus(String id, AppointmentStatus status) async {
|
||||
try {
|
||||
await _databaseHelper.updateAppointmentStatus(id, status);
|
||||
|
||||
// 更新内存缓存
|
||||
final cacheKey = 'appointment_$id';
|
||||
final cachedData = await _localStorage.getString(cacheKey);
|
||||
if (cachedData != null) {
|
||||
final appointment = Appointment.fromJson(jsonDecode(cachedData));
|
||||
final updatedAppointment = appointment.copyWith(status: status);
|
||||
await _localStorage.setString(
|
||||
cacheKey,
|
||||
jsonEncode(updatedAppointment.toJson()),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
throw RepositoryException('更新预约状态失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 数据模型 (`src/models/`)
|
||||
|
||||
#### 预约模型
|
||||
```dart
|
||||
@freezed
|
||||
class Appointment with _$Appointment {
|
||||
const factory Appointment({
|
||||
required String id,
|
||||
required String userId,
|
||||
required String storeId,
|
||||
required ServiceType serviceType,
|
||||
required DateTime appointmentTime,
|
||||
required AppointmentStatus status,
|
||||
required VehicleInfo vehicleInfo,
|
||||
String? description,
|
||||
List<String>? attachments,
|
||||
@JsonKey(name: 'created_at') DateTime? createdAt,
|
||||
@JsonKey(name: 'updated_at') DateTime? updatedAt,
|
||||
Store? store,
|
||||
List<ServiceItem>? serviceItems,
|
||||
PaymentInfo? paymentInfo,
|
||||
}) = _Appointment;
|
||||
|
||||
factory Appointment.fromJson(Map<String, dynamic> json) =>
|
||||
_$AppointmentFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class AppointmentCreateRequest with _$AppointmentCreateRequest {
|
||||
const factory AppointmentCreateRequest({
|
||||
required String storeId,
|
||||
required ServiceType serviceType,
|
||||
required DateTime appointmentTime,
|
||||
required VehicleInfo vehicleInfo,
|
||||
String? description,
|
||||
List<String>? attachments,
|
||||
List<String>? serviceItemIds,
|
||||
}) = _AppointmentCreateRequest;
|
||||
|
||||
factory AppointmentCreateRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$AppointmentCreateRequestFromJson(json);
|
||||
}
|
||||
```
|
||||
|
||||
#### 服务进度模型
|
||||
```dart
|
||||
@freezed
|
||||
class ServiceProgress with _$ServiceProgress {
|
||||
const factory ServiceProgress({
|
||||
required String appointmentId,
|
||||
required ServiceStatus status,
|
||||
required List<ServiceStep> steps,
|
||||
required double progressPercentage,
|
||||
@JsonKey(name: 'estimated_completion') DateTime? estimatedCompletion,
|
||||
@JsonKey(name: 'actual_completion') DateTime? actualCompletion,
|
||||
Technician? assignedTechnician,
|
||||
List<ServicePhoto>? photos,
|
||||
String? currentStepNote,
|
||||
}) = _ServiceProgress;
|
||||
|
||||
factory ServiceProgress.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceProgressFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ServiceStep with _$ServiceStep {
|
||||
const factory ServiceStep({
|
||||
required String id,
|
||||
required String name,
|
||||
required String description,
|
||||
required ServiceStepStatus status,
|
||||
required int orderIndex,
|
||||
@JsonKey(name: 'started_at') DateTime? startedAt,
|
||||
@JsonKey(name: 'completed_at') DateTime? completedAt,
|
||||
String? note,
|
||||
List<String>? photoIds,
|
||||
Duration? estimatedDuration,
|
||||
Duration? actualDuration,
|
||||
}) = _ServiceStep;
|
||||
|
||||
factory ServiceStep.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceStepFromJson(json);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 枚举定义 (`src/enums/`)
|
||||
|
||||
```dart
|
||||
enum AppointmentStatus {
|
||||
@JsonValue('pending')
|
||||
pending,
|
||||
@JsonValue('confirmed')
|
||||
confirmed,
|
||||
@JsonValue('inProgress')
|
||||
inProgress,
|
||||
@JsonValue('completed')
|
||||
completed,
|
||||
@JsonValue('cancelled')
|
||||
cancelled,
|
||||
@JsonValue('noShow')
|
||||
noShow,
|
||||
}
|
||||
|
||||
enum ServiceType {
|
||||
@JsonValue('maintenance')
|
||||
maintenance,
|
||||
@JsonValue('repair')
|
||||
repair,
|
||||
@JsonValue('inspection')
|
||||
inspection,
|
||||
@JsonValue('bodywork')
|
||||
bodywork,
|
||||
@JsonValue('insurance')
|
||||
insurance,
|
||||
@JsonValue('emergency')
|
||||
emergency,
|
||||
@JsonValue('recall')
|
||||
recall,
|
||||
}
|
||||
|
||||
enum ServiceStatus {
|
||||
@JsonValue('waiting')
|
||||
waiting,
|
||||
@JsonValue('inProgress')
|
||||
inProgress,
|
||||
@JsonValue('completed')
|
||||
completed,
|
||||
@JsonValue('paused')
|
||||
paused,
|
||||
@JsonValue('cancelled')
|
||||
cancelled,
|
||||
}
|
||||
|
||||
enum ServiceStepStatus {
|
||||
@JsonValue('pending')
|
||||
pending,
|
||||
@JsonValue('inProgress')
|
||||
inProgress,
|
||||
@JsonValue('completed')
|
||||
completed,
|
||||
@JsonValue('skipped')
|
||||
skipped,
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 异常定义 (`src/exceptions/`)
|
||||
|
||||
```dart
|
||||
abstract class AfterSalesException implements Exception {
|
||||
final String message;
|
||||
final String? code;
|
||||
final dynamic details;
|
||||
|
||||
const AfterSalesException(this.message, {this.code, this.details});
|
||||
|
||||
@override
|
||||
String toString() => 'AfterSalesException: $message';
|
||||
}
|
||||
|
||||
class AppointmentException extends AfterSalesException {
|
||||
const AppointmentException(String message, {String? code, dynamic details})
|
||||
: super(message, code: code, details: details);
|
||||
}
|
||||
|
||||
class ServiceException extends AfterSalesException {
|
||||
const ServiceException(String message, {String? code, dynamic details})
|
||||
: super(message, code: code, details: details);
|
||||
}
|
||||
|
||||
class PaymentException extends AfterSalesException {
|
||||
const PaymentException(String message, {String? code, dynamic details})
|
||||
: super(message, code: code, details: details);
|
||||
}
|
||||
|
||||
class NetworkException extends AfterSalesException {
|
||||
const NetworkException(String message, {String? code, dynamic details})
|
||||
: super(message, code: code, details: details);
|
||||
}
|
||||
|
||||
class RepositoryException extends AfterSalesException {
|
||||
const RepositoryException(String message, {String? code, dynamic details})
|
||||
: super(message, code: code, details: details);
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 结果封装 (`src/utils/result.dart`)
|
||||
|
||||
```dart
|
||||
@freezed
|
||||
class Result<T> with _$Result<T> {
|
||||
const factory Result.success(T data) = Success<T>;
|
||||
const factory Result.failure(Exception error) = Failure<T>;
|
||||
|
||||
bool get isSuccess => this is Success<T>;
|
||||
bool get isFailure => this is Failure<T>;
|
||||
|
||||
T? get data => mapOrNull(success: (success) => success.data);
|
||||
Exception? get error => mapOrNull(failure: (failure) => failure.error);
|
||||
}
|
||||
|
||||
extension ResultExtensions<T> on Result<T> {
|
||||
R fold<R>(
|
||||
R Function(T data) onSuccess,
|
||||
R Function(Exception error) onFailure,
|
||||
) {
|
||||
return when(
|
||||
success: onSuccess,
|
||||
failure: onFailure,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Result<R>> mapAsync<R>(
|
||||
Future<R> Function(T data) mapper,
|
||||
) async {
|
||||
return fold(
|
||||
(data) async {
|
||||
try {
|
||||
final result = await mapper(data);
|
||||
return Result.success(result);
|
||||
} catch (e) {
|
||||
return Result.failure(Exception(e.toString()));
|
||||
}
|
||||
},
|
||||
(error) => Future.value(Result.failure(error)),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础使用
|
||||
```dart
|
||||
class AfterSalesExample {
|
||||
final AppointmentService _appointmentService;
|
||||
final ServiceProgressService _progressService;
|
||||
|
||||
AfterSalesExample(this._appointmentService, this._progressService);
|
||||
|
||||
Future<void> createAppointmentExample() async {
|
||||
final request = AppointmentCreateRequest(
|
||||
storeId: 'store_123',
|
||||
serviceType: ServiceType.maintenance,
|
||||
appointmentTime: DateTime.now().add(Duration(days: 1)),
|
||||
vehicleInfo: VehicleInfo(
|
||||
vin: 'WVWAA71K08W201030',
|
||||
licensePlate: '京A12345',
|
||||
model: 'ID.4 CROZZ',
|
||||
),
|
||||
description: '常规保养',
|
||||
);
|
||||
|
||||
final result = await _appointmentService.createAppointment(request);
|
||||
|
||||
result.fold(
|
||||
(appointment) {
|
||||
print('预约创建成功: ${appointment.id}');
|
||||
},
|
||||
(error) {
|
||||
print('预约创建失败: ${error.toString()}');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> trackServiceProgressExample(String appointmentId) async {
|
||||
final result = await _progressService.getServiceProgress(appointmentId);
|
||||
|
||||
result.fold(
|
||||
(progress) {
|
||||
print('服务进度: ${progress.progressPercentage}%');
|
||||
print('当前步骤: ${progress.steps.where((s) => s.status == ServiceStepStatus.inProgress).first.name}');
|
||||
},
|
||||
(error) {
|
||||
print('获取进度失败: ${error.toString()}');
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 依赖注入配置
|
||||
```dart
|
||||
class AfterSalesDI {
|
||||
static void setupDependencies(GetIt locator) {
|
||||
// 注册仓库
|
||||
locator.registerLazySingleton<AppointmentRepository>(
|
||||
() => AppointmentRepositoryImpl(
|
||||
locator<LocalStorage>(),
|
||||
locator<DatabaseHelper>(),
|
||||
),
|
||||
);
|
||||
|
||||
locator.registerLazySingleton<ServiceProgressRepository>(
|
||||
() => ServiceProgressRepositoryImpl(
|
||||
locator<LocalStorage>(),
|
||||
locator<DatabaseHelper>(),
|
||||
),
|
||||
);
|
||||
|
||||
// 注册服务
|
||||
locator.registerLazySingleton<AppointmentService>(
|
||||
() => AppointmentService(
|
||||
locator<AppointmentRepository>(),
|
||||
locator<NetworkService>(),
|
||||
),
|
||||
);
|
||||
|
||||
locator.registerLazySingleton<ServiceProgressService>(
|
||||
() => ServiceProgressService(
|
||||
locator<ServiceProgressRepository>(),
|
||||
locator<NetworkService>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 单元测试
|
||||
```dart
|
||||
void main() {
|
||||
group('AppointmentService', () {
|
||||
late AppointmentService appointmentService;
|
||||
late MockAppointmentRepository mockRepository;
|
||||
late MockNetworkService mockNetworkService;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockAppointmentRepository();
|
||||
mockNetworkService = MockNetworkService();
|
||||
appointmentService = AppointmentService(mockRepository, mockNetworkService);
|
||||
});
|
||||
|
||||
test('should create appointment successfully', () async {
|
||||
// Arrange
|
||||
final request = AppointmentCreateRequest(
|
||||
storeId: 'store_123',
|
||||
serviceType: ServiceType.maintenance,
|
||||
appointmentTime: DateTime.now().add(Duration(days: 1)),
|
||||
vehicleInfo: VehicleInfo(vin: 'TEST123'),
|
||||
);
|
||||
|
||||
final expectedAppointment = Appointment(
|
||||
id: 'appointment_123',
|
||||
userId: 'user_123',
|
||||
storeId: request.storeId,
|
||||
serviceType: request.serviceType,
|
||||
appointmentTime: request.appointmentTime,
|
||||
status: AppointmentStatus.pending,
|
||||
vehicleInfo: request.vehicleInfo,
|
||||
);
|
||||
|
||||
when(mockNetworkService.post('/appointments', data: any))
|
||||
.thenAnswer((_) async => ApiResponse(data: expectedAppointment.toJson()));
|
||||
|
||||
// Act
|
||||
final result = await appointmentService.createAppointment(request);
|
||||
|
||||
// Assert
|
||||
expect(result.isSuccess, true);
|
||||
expect(result.data?.id, 'appointment_123');
|
||||
verify(mockRepository.saveAppointment(any)).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### API设计原则
|
||||
1. **统一响应格式**: 所有API使用统一的响应格式
|
||||
2. **错误处理**: 完善的错误码和错误信息
|
||||
3. **数据验证**: 严格的输入数据验证
|
||||
4. **幂等性**: 关键操作支持幂等性
|
||||
|
||||
### 缓存策略
|
||||
1. **多级缓存**: 内存缓存 + 本地存储 + 网络
|
||||
2. **缓存失效**: 基于时间和事件的缓存失效
|
||||
3. **数据一致性**: 保证缓存与服务器数据一致
|
||||
4. **离线支持**: 关键数据支持离线访问
|
||||
|
||||
## 总结
|
||||
|
||||
`clr_after_sales` 模块作为售后服务的核心SDK,通过完善的架构设计和标准化的接口封装,为售后服务应用提供了稳定可靠的技术支撑。模块采用了领域驱动设计思想,具有良好的可测试性和可维护性,能够支撑复杂的售后业务场景。
|
||||
659
after_sales/oneapp_after_sales.md
Normal file
659
after_sales/oneapp_after_sales.md
Normal file
@@ -0,0 +1,659 @@
|
||||
# OneApp After Sales 售后服务主模块
|
||||
|
||||
## 模块概述
|
||||
|
||||
`oneapp_after_sales` 是 OneApp 售后服务模块群中的主应用模块,负责为用户提供完整的汽车售后服务功能。该模块包含维修预约、服务跟踪、客户服务、支付结算等核心功能,为车主提供便捷的售后服务体验。
|
||||
|
||||
### 基本信息
|
||||
- **模块名称**: oneapp_after_sales
|
||||
- **版本**: 0.0.1
|
||||
- **描述**: 售后服务主应用模块
|
||||
- **Flutter 版本**: >=1.17.0
|
||||
- **Dart 版本**: >=3.0.0 <4.0.0
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 核心功能
|
||||
1. **维修预约管理**
|
||||
- 在线预约维修服务
|
||||
- 预约时间智能调度
|
||||
- 服务门店选择和导航
|
||||
- 预约状态实时跟踪
|
||||
|
||||
2. **服务流程跟踪**
|
||||
- 维修进度实时更新
|
||||
- 服务照片和视频记录
|
||||
- 技师服务过程展示
|
||||
- 完工通知和验收
|
||||
|
||||
3. **客户服务中心**
|
||||
- 在线客服聊天
|
||||
- 投诉建议提交
|
||||
- 服务评价反馈
|
||||
- FAQ常见问题
|
||||
|
||||
4. **支付和结算**
|
||||
- 多种支付方式支持
|
||||
- 费用明细透明展示
|
||||
- 电子发票生成
|
||||
- 会员优惠计算
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
lib/
|
||||
├── oneapp_after_sales.dart # 模块入口文件
|
||||
├── src/ # 源代码目录
|
||||
│ ├── appointment/ # 预约管理
|
||||
│ ├── service/ # 服务跟踪
|
||||
│ ├── customer/ # 客户服务
|
||||
│ ├── payment/ # 支付结算
|
||||
│ ├── store/ # 门店管理
|
||||
│ ├── pages/ # 页面组件
|
||||
│ ├── widgets/ # 自定义组件
|
||||
│ ├── models/ # 数据模型
|
||||
│ └── utils/ # 工具类
|
||||
├── assets/ # 资源文件
|
||||
└── l10n/ # 国际化文件
|
||||
```
|
||||
|
||||
### 依赖关系
|
||||
|
||||
#### 基础框架依赖
|
||||
- `basic_modular: ^0.2.3` - 模块化框架
|
||||
- `basic_modular_route: ^0.2.1` - 路由管理
|
||||
- `basic_intl: ^0.2.0` - 国际化支持
|
||||
- `basic_webview: ^0.2.4+4` - WebView组件
|
||||
|
||||
#### 地图和位置服务
|
||||
- `ui_mapview: ^0.2.18` - 地图视图组件
|
||||
- `amap_flutter_location: ^3.0.3` - 高德地图定位
|
||||
- `clr_geo: ^0.2.16+1` - 地理位置服务
|
||||
- `location_service_check: ^1.0.1` - 位置服务检查
|
||||
|
||||
#### 支付服务
|
||||
- `kit_alipay: ^0.2.0` - 支付宝支付
|
||||
- `fluwx: ^4.4.3` - 微信支付
|
||||
|
||||
#### 媒体和文件处理
|
||||
- `photo_view: ^0.14.0` - 图片预览
|
||||
- `photo_gallery: 2.2.1+1` - 相册选择
|
||||
- `signature: ^5.4.0` - 电子签名
|
||||
|
||||
#### 工具依赖
|
||||
- `permission_handler: ^10.3.0` - 权限管理
|
||||
- `url_launcher: ^6.1.14` - URL启动
|
||||
- `dio: any` - 网络请求
|
||||
- `common_utils: ^2.1.0` - 通用工具
|
||||
- `flutter_color_plugin: ^1.1.0` - 颜色工具
|
||||
|
||||
## 核心模块分析
|
||||
|
||||
### 1. 预约管理 (`src/appointment/`)
|
||||
|
||||
#### 预约创建流程
|
||||
```dart
|
||||
class AppointmentBookingPage extends StatefulWidget {
|
||||
@override
|
||||
_AppointmentBookingPageState createState() => _AppointmentBookingPageState();
|
||||
}
|
||||
|
||||
class _AppointmentBookingPageState extends State<AppointmentBookingPage> {
|
||||
final AppointmentBookingController _controller = AppointmentBookingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('维修预约')),
|
||||
body: Stepper(
|
||||
currentStep: _controller.currentStep,
|
||||
onStepTapped: _controller.onStepTapped,
|
||||
controlsBuilder: _buildStepControls,
|
||||
steps: [
|
||||
Step(
|
||||
title: Text('选择服务'),
|
||||
content: ServiceTypeSelector(
|
||||
selectedType: _controller.selectedServiceType,
|
||||
onTypeSelected: _controller.onServiceTypeSelected,
|
||||
),
|
||||
isActive: _controller.currentStep >= 0,
|
||||
),
|
||||
Step(
|
||||
title: Text('选择门店'),
|
||||
content: StoreSelector(
|
||||
location: _controller.userLocation,
|
||||
selectedStore: _controller.selectedStore,
|
||||
onStoreSelected: _controller.onStoreSelected,
|
||||
),
|
||||
isActive: _controller.currentStep >= 1,
|
||||
),
|
||||
Step(
|
||||
title: Text('预约时间'),
|
||||
content: TimeSlotSelector(
|
||||
availableSlots: _controller.availableTimeSlots,
|
||||
selectedSlot: _controller.selectedTimeSlot,
|
||||
onSlotSelected: _controller.onTimeSlotSelected,
|
||||
),
|
||||
isActive: _controller.currentStep >= 2,
|
||||
),
|
||||
Step(
|
||||
title: Text('车辆信息'),
|
||||
content: VehicleInfoForm(
|
||||
vehicleInfo: _controller.vehicleInfo,
|
||||
onInfoChanged: _controller.onVehicleInfoChanged,
|
||||
),
|
||||
isActive: _controller.currentStep >= 3,
|
||||
),
|
||||
Step(
|
||||
title: Text('确认预约'),
|
||||
content: AppointmentSummary(
|
||||
appointment: _controller.appointmentSummary,
|
||||
),
|
||||
isActive: _controller.currentStep >= 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 预约管理器
|
||||
```dart
|
||||
class AppointmentManager {
|
||||
static Future<List<Appointment>> getUserAppointments() async {
|
||||
try {
|
||||
final response = await AfterSalesAPI.getUserAppointments();
|
||||
return response.data.map((json) => Appointment.fromJson(json)).toList();
|
||||
} catch (e) {
|
||||
throw AppointmentException('获取预约列表失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Appointment> createAppointment(AppointmentRequest request) async {
|
||||
try {
|
||||
final response = await AfterSalesAPI.createAppointment(request.toJson());
|
||||
return Appointment.fromJson(response.data);
|
||||
} catch (e) {
|
||||
throw AppointmentException('创建预约失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> cancelAppointment(String appointmentId) async {
|
||||
try {
|
||||
await AfterSalesAPI.cancelAppointment(appointmentId);
|
||||
return true;
|
||||
} catch (e) {
|
||||
throw AppointmentException('取消预约失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 服务跟踪 (`src/service/`)
|
||||
|
||||
#### 服务进度展示
|
||||
```dart
|
||||
class ServiceProgressPage extends StatefulWidget {
|
||||
final String appointmentId;
|
||||
|
||||
const ServiceProgressPage({Key? key, required this.appointmentId}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ServiceProgressPageState createState() => _ServiceProgressPageState();
|
||||
}
|
||||
|
||||
class _ServiceProgressPageState extends State<ServiceProgressPage> {
|
||||
ServiceProgress? _progress;
|
||||
Timer? _progressTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadServiceProgress();
|
||||
_startProgressPolling();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_progress == null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('服务进度')),
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('服务进度')),
|
||||
body: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ServiceStatusCard(progress: _progress!),
|
||||
SizedBox(height: 16),
|
||||
ServiceTimelineWidget(steps: _progress!.steps),
|
||||
SizedBox(height: 16),
|
||||
if (_progress!.photos.isNotEmpty) ...[
|
||||
ServicePhotosSection(photos: _progress!.photos),
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
TechnicianInfoCard(technician: _progress!.technician),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _startProgressPolling() {
|
||||
_progressTimer = Timer.periodic(Duration(seconds: 30), (timer) {
|
||||
_loadServiceProgress();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_progressTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 服务时间轴组件
|
||||
```dart
|
||||
class ServiceTimelineWidget extends StatelessWidget {
|
||||
final List<ServiceStep> steps;
|
||||
|
||||
const ServiceTimelineWidget({Key? key, required this.steps}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'服务进度',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Timeline.tileBuilder(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
builder: TimelineTileBuilder.connected(
|
||||
itemCount: steps.length,
|
||||
connectionDirection: ConnectionDirection.before,
|
||||
itemExtentBuilder: (_, __) => 80.0,
|
||||
contentsBuilder: (context, index) {
|
||||
final step = steps[index];
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
step.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: step.isCompleted ? Colors.green : Colors.grey,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
step.description,
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
if (step.completedAt != null)
|
||||
Text(
|
||||
DateFormat('MM-dd HH:mm').format(step.completedAt!),
|
||||
style: TextStyle(fontSize: 10, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
indicatorBuilder: (context, index) {
|
||||
final step = steps[index];
|
||||
return DotIndicator(
|
||||
color: step.isCompleted ? Colors.green : Colors.grey,
|
||||
child: Icon(
|
||||
step.isCompleted ? Icons.check : Icons.schedule,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
},
|
||||
connectorBuilder: (context, index, type) {
|
||||
return SolidLineConnector(
|
||||
color: steps[index].isCompleted ? Colors.green : Colors.grey,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 客户服务 (`src/customer/`)
|
||||
|
||||
#### 在线客服聊天
|
||||
```dart
|
||||
class CustomerServiceChatPage extends StatefulWidget {
|
||||
@override
|
||||
_CustomerServiceChatPageState createState() => _CustomerServiceChatPageState();
|
||||
}
|
||||
|
||||
class _CustomerServiceChatPageState extends State<CustomerServiceChatPage> {
|
||||
final List<ChatMessage> _messages = [];
|
||||
final TextEditingController _textController = TextEditingController();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
late WebSocketChannel _channel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_connectToCustomerService();
|
||||
_loadChatHistory();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('在线客服'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.phone),
|
||||
onPressed: _makePhoneCall,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: EdgeInsets.all(16),
|
||||
itemCount: _messages.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ChatMessageBubble(message: _messages[index]);
|
||||
},
|
||||
),
|
||||
),
|
||||
_buildMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMessageInput() {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border(top: BorderSide(color: Colors.grey[300]!)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.add),
|
||||
onPressed: _showAttachmentOptions,
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _textController,
|
||||
decoration: InputDecoration(
|
||||
hintText: '请输入消息...',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
),
|
||||
onSubmitted: _sendMessage,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: Icon(Icons.send, color: Theme.of(context).primaryColor),
|
||||
onPressed: () => _sendMessage(_textController.text),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 支付结算 (`src/payment/`)
|
||||
|
||||
#### 支付管理器
|
||||
```dart
|
||||
class PaymentManager {
|
||||
static Future<PaymentResult> processPayment({
|
||||
required PaymentMethod method,
|
||||
required double amount,
|
||||
required String orderId,
|
||||
Map<String, dynamic>? extras,
|
||||
}) async {
|
||||
try {
|
||||
switch (method) {
|
||||
case PaymentMethod.alipay:
|
||||
return await _processAlipayPayment(amount, orderId, extras);
|
||||
case PaymentMethod.wechat:
|
||||
return await _processWechatPayment(amount, orderId, extras);
|
||||
case PaymentMethod.card:
|
||||
return await _processBankCardPayment(amount, orderId, extras);
|
||||
default:
|
||||
throw PaymentException('不支持的支付方式');
|
||||
}
|
||||
} catch (e) {
|
||||
return PaymentResult.failure(error: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static Future<PaymentResult> _processAlipayPayment(
|
||||
double amount,
|
||||
String orderId,
|
||||
Map<String, dynamic>? extras,
|
||||
) async {
|
||||
// 获取支付宝支付参数
|
||||
final paymentParams = await AfterSalesAPI.getAlipayParams(
|
||||
amount: amount,
|
||||
orderId: orderId,
|
||||
extras: extras,
|
||||
);
|
||||
|
||||
// 调用支付宝SDK
|
||||
final result = await AlipayKit.pay(paymentParams.orderString);
|
||||
|
||||
if (result.resultStatus == '9000') {
|
||||
return PaymentResult.success(
|
||||
transactionId: result.result,
|
||||
method: PaymentMethod.alipay,
|
||||
);
|
||||
} else {
|
||||
return PaymentResult.failure(error: result.memo);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 数据模型
|
||||
|
||||
### 预约模型
|
||||
```dart
|
||||
@freezed
|
||||
class Appointment with _$Appointment {
|
||||
const factory Appointment({
|
||||
required String id,
|
||||
required String userId,
|
||||
required ServiceType serviceType,
|
||||
required Store store,
|
||||
required DateTime appointmentTime,
|
||||
required VehicleInfo vehicleInfo,
|
||||
required AppointmentStatus status,
|
||||
String? description,
|
||||
List<String>? attachments,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) = _Appointment;
|
||||
|
||||
factory Appointment.fromJson(Map<String, dynamic> json) =>
|
||||
_$AppointmentFromJson(json);
|
||||
}
|
||||
|
||||
enum AppointmentStatus {
|
||||
pending, // 待确认
|
||||
confirmed, // 已确认
|
||||
inProgress, // 进行中
|
||||
completed, // 已完成
|
||||
cancelled, // 已取消
|
||||
}
|
||||
|
||||
enum ServiceType {
|
||||
maintenance, // 保养
|
||||
repair, // 维修
|
||||
inspection, // 检测
|
||||
bodywork, // 钣喷
|
||||
insurance, // 保险
|
||||
emergency, // 紧急救援
|
||||
}
|
||||
```
|
||||
|
||||
### 服务进度模型
|
||||
```dart
|
||||
@freezed
|
||||
class ServiceProgress with _$ServiceProgress {
|
||||
const factory ServiceProgress({
|
||||
required String appointmentId,
|
||||
required ServiceStatus status,
|
||||
required List<ServiceStep> steps,
|
||||
required Technician technician,
|
||||
required Store store,
|
||||
@Default([]) List<ServicePhoto> photos,
|
||||
String? currentStepDescription,
|
||||
DateTime? estimatedCompletion,
|
||||
double? totalCost,
|
||||
}) = _ServiceProgress;
|
||||
|
||||
factory ServiceProgress.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceProgressFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ServiceStep with _$ServiceStep {
|
||||
const factory ServiceStep({
|
||||
required String id,
|
||||
required String title,
|
||||
required String description,
|
||||
required bool isCompleted,
|
||||
DateTime? completedAt,
|
||||
String? note,
|
||||
}) = _ServiceStep;
|
||||
|
||||
factory ServiceStep.fromJson(Map<String, dynamic> json) =>
|
||||
_$ServiceStepFromJson(json);
|
||||
}
|
||||
```
|
||||
|
||||
## 业务流程实现
|
||||
|
||||
### 预约流程管理
|
||||
```dart
|
||||
class AppointmentWorkflow {
|
||||
static Future<AppointmentResult> executeBookingWorkflow(
|
||||
AppointmentRequest request,
|
||||
) async {
|
||||
try {
|
||||
// 1. 验证预约时间可用性
|
||||
final availability = await _checkTimeAvailability(
|
||||
request.storeId,
|
||||
request.appointmentTime,
|
||||
);
|
||||
|
||||
if (!availability.isAvailable) {
|
||||
return AppointmentResult.failure(
|
||||
error: '所选时间不可用,建议时间:${availability.suggestedTimes}',
|
||||
);
|
||||
}
|
||||
|
||||
// 2. 创建预约记录
|
||||
final appointment = await AppointmentManager.createAppointment(request);
|
||||
|
||||
// 3. 发送确认通知
|
||||
await NotificationService.sendAppointmentConfirmation(appointment);
|
||||
|
||||
// 4. 更新门店排班
|
||||
await StoreScheduleService.updateSchedule(
|
||||
appointment.store.id,
|
||||
appointment.appointmentTime,
|
||||
);
|
||||
|
||||
return AppointmentResult.success(appointment: appointment);
|
||||
} catch (e) {
|
||||
return AppointmentResult.failure(error: e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 集成和测试
|
||||
|
||||
### API集成
|
||||
```dart
|
||||
class AfterSalesAPI {
|
||||
static const String baseUrl = 'https://api.oneapp.com/after-sales';
|
||||
static final Dio _dio = Dio();
|
||||
|
||||
static Future<ApiResponse> createAppointment(Map<String, dynamic> data) async {
|
||||
final response = await _dio.post('$baseUrl/appointments', data: data);
|
||||
return ApiResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
static Future<ApiResponse> getServiceProgress(String appointmentId) async {
|
||||
final response = await _dio.get('$baseUrl/appointments/$appointmentId/progress');
|
||||
return ApiResponse.fromJson(response.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 单元测试
|
||||
```dart
|
||||
void main() {
|
||||
group('AppointmentManager', () {
|
||||
test('should create appointment successfully', () async {
|
||||
final request = AppointmentRequest(
|
||||
serviceType: ServiceType.maintenance,
|
||||
storeId: 'store_123',
|
||||
appointmentTime: DateTime.now().add(Duration(days: 1)),
|
||||
vehicleInfo: VehicleInfo(vin: 'TEST123'),
|
||||
);
|
||||
|
||||
final appointment = await AppointmentManager.createAppointment(request);
|
||||
|
||||
expect(appointment.id, isNotEmpty);
|
||||
expect(appointment.serviceType, ServiceType.maintenance);
|
||||
expect(appointment.status, AppointmentStatus.pending);
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 用户体验优化
|
||||
1. **流程简化**: 减少预约步骤,提供智能推荐
|
||||
2. **实时反馈**: 及时更新服务进度和状态
|
||||
3. **多渠道沟通**: 提供电话、在线客服等多种联系方式
|
||||
4. **透明计费**: 清晰展示费用明细和计算过程
|
||||
|
||||
### 性能优化
|
||||
1. **数据缓存**: 缓存门店信息和服务数据
|
||||
2. **图片优化**: 压缩和缓存服务照片
|
||||
3. **网络优化**: 使用CDN加速资源加载
|
||||
4. **离线支持**: 关键信息支持离线查看
|
||||
|
||||
## 总结
|
||||
|
||||
`oneapp_after_sales` 模块作为售后服务的主要入口,通过完整的业务流程设计和用户友好的界面实现,为车主提供了便捷高效的售后服务体验。模块具有良好的扩展性和可维护性,能够适应不断变化的售后服务需求。
|
||||
Reference in New Issue
Block a user