Files
oneapp_docs/after_sales/oneapp_after_sales.md
2025-09-24 14:08:54 +08:00

19 KiB

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/)

预约创建流程

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,
          ),
        ],
      ),
    );
  }
}

预约管理器

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/)

服务进度展示

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();
  }
}

服务时间轴组件

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/)

在线客服聊天

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/)

支付管理器

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);
    }
  }
}

数据模型

预约模型

@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,       // 紧急救援
}

服务进度模型

@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);
}

业务流程实现

预约流程管理

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集成

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);
  }
}

单元测试

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 模块作为售后服务的主要入口,通过完整的业务流程设计和用户友好的界面实现,为车主提供了便捷高效的售后服务体验。模块具有良好的扩展性和可维护性,能够适应不断变化的售后服务需求。