JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统源码小程序+公众号+h5

JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统:数字化革新本地生活服务生态

在当今快节奏的都市生活中,随着居民消费升级和服务需求多元化,传统本地服务行业正面临前所未有的转型压力。传统的家政服务、美容美发、洗车保洁、搬家维修、家装等服务模式存在信息不透明、预约效率低、服务质量难保障、支付结算不安全等行业痛点。JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统应运而生,以其全面的技术架构和创新的商业模式,为本地生活服务行业带来革命性变革。这套JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统整合了小程序、公众号和H5等多端入口,通过springboot+mybatisplus+mysql构建稳定高效的后台服务,uniapp实现用户端跨平台开发,vue+elementUi打造专业管理后台,彻底解决了传统本地服务行业中服务标准不统一、预约流程繁琐、服务人员管理困难、客户体验差等核心问题。用户可以通过JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统随时随地预约各类生活服务,系统智能匹配附近优质服务商,实时跟踪服务进度,确保服务过程透明可控。这种JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统真正实现了本地生活服务行业的数字化转型,构建起连接用户、服务商、平台方的完整生态系统。

系统架构设计与核心技术优势

本JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统采用前后端分离的现代化架构,后端基于SpringBoot框架提供稳定的RESTful API接口,前端通过uniapp实现一次开发多端部署,管理端使用Vue.js配合ElementUI组件库。这种JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统架构确保了系统的高性能、高可用性和易维护性。

核心架构代码示例:

复制代码
// SpringBoot主启动类配置
@SpringBootApplication
@MapperScan("com.localservice.mapper")
@EnableScheduling
@EnableAsync
@EnableCaching
public class LocalServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(LocalServiceApplication.class, args);
    }
}

// MybatisPlus配置类
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 动态表名插件
        interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor());
        return interceptor;
    }
}

// Redis缓存配置
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LazyCollectionDefaultingTyping, 
            ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        
        return template;
    }
}
数据库设计与数据持久层实现

JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统采用MySQL数据库,通过精心设计的表结构确保数据一致性和完整性。核心表包括服务订单表、服务商信息表、服务类别表、用户评价表等。

实体类和Mapper层代码示例:

复制代码
// 服务订单实体类
@Data
@TableName("service_order")
@ApiModel(value = "ServiceOrder对象", description = "服务订单表")
public class ServiceOrder {
    @TableId(type = IdType.ASSIGN_ID)
    @ApiModelProperty("订单ID")
    private Long orderId;
    
    @ApiModelProperty("订单编号")
    private String orderNo;
    
    @ApiModelProperty("用户ID")
    private Long userId;
    
    @ApiModelProperty("服务商ID")
    private Long merchantId;
    
    @ApiModelProperty("服务人员ID")
    private Long staffId;
    
    @ApiModelProperty("服务类别ID")
    private Long categoryId;
    
    @ApiModelProperty("服务项目ID")
    private Long serviceItemId;
    
    @ApiModelProperty("订单状态")
    private Integer orderStatus;
    
    @ApiModelProperty("预约时间")
    private LocalDateTime appointmentTime;
    
    @ApiModelProperty("服务地址")
    private String serviceAddress;
    
    @ApiModelProperty("订单金额")
    private BigDecimal orderAmount;
    
    @ApiModelProperty("实际支付金额")
    private BigDecimal payAmount;
    
    @ApiModelProperty("支付状态")
    private Integer payStatus;
    
    @ApiModelProperty("订单备注")
    private String orderRemark;
    
    @ApiModelProperty("客户联系电话")
    private String customerPhone;
    
    @ApiModelProperty("服务开始时间")
    private LocalDateTime serviceStartTime;
    
    @ApiModelProperty("服务结束时间")
    private LocalDateTime serviceEndTime;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

// 服务商信息实体类
@Data
@TableName("service_merchant")
@ApiModel(value = "ServiceMerchant对象", description = "服务商信息表")
public class ServiceMerchant {
    @TableId(type = IdType.AUTO)
    @ApiModelProperty("服务商ID")
    private Long merchantId;
    
    @ApiModelProperty("服务商名称")
    private String merchantName;
    
    @ApiModelProperty("服务类别")
    private String serviceCategories;
    
    @ApiModelProperty("联系人")
    private String contactPerson;
    
    @ApiModelProperty("联系电话")
    private String contactPhone;
    
    @ApiModelProperty("服务区域")
    private String serviceArea;
    
    @ApiModelProperty("详细地址")
    private String address;
    
    @ApiModelProperty("经度")
    private BigDecimal longitude;
    
    @ApiModelProperty("纬度")
    private BigDecimal latitude;
    
    @ApiModelProperty("营业时间")
    private String businessHours;
    
    @ApiModelProperty("商家评分")
    private BigDecimal rating;
    
    @ApiModelProperty("月销量")
    private Integer monthlySales;
    
    @ApiModelProperty("认证状态")
    private Integer auditStatus;
    
    @ApiModelProperty("商家介绍")
    private String introduction;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}

// Mapper接口定义
public interface ServiceOrderMapper extends BaseMapper<ServiceOrder> {
    
    @Select("SELECT * FROM service_order WHERE merchant_id = #{merchantId} AND order_status = #{status}")
    List<ServiceOrder> selectByMerchantAndStatus(@Param("merchantId") Long merchantId, 
                                               @Param("status") Integer status);
    
    @Select("<script>" +
            "SELECT COUNT(*) FROM service_order WHERE 1=1 " +
            "<if test='merchantId != null'> AND merchant_id = #{merchantId} </if>" +
            "<if test='startTime != null'> AND create_time >= #{startTime} </if>" +
            "<if test='endTime != null'> AND create_time <= #{endTime} </if>" +
            "<if test='status != null'> AND order_status = #{status} </if>" +
            "</script>")
    Long countOrders(@Param("merchantId") Long merchantId,
                    @Param("startTime") LocalDateTime startTime,
                    @Param("endTime") LocalDateTime endTime,
                    @Param("status") Integer status);
    
    @Update("UPDATE service_order SET order_status = #{status} WHERE order_id = #{orderId}")
    int updateOrderStatus(@Param("orderId") Long orderId, @Param("status") Integer status);
    
    @Select("SELECT * FROM service_order WHERE user_id = #{userId} ORDER BY create_time DESC LIMIT #{limit}")
    List<ServiceOrder> selectRecentOrdersByUser(@Param("userId") Long userId, 
                                              @Param("limit") Integer limit);
}
业务逻辑层核心实现

JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统的业务逻辑层采用SpringBoot+MyBatisPlus技术栈,通过@Service注解实现业务逻辑,@Transactional确保数据一致性。

业务服务层代码示例:

复制代码
// 预约服务接口
public interface BookingService extends IService<ServiceOrder> {
    
    OrderCreateResultVO createServiceOrder(OrderCreateDTO createDTO);
    
    boolean confirmOrder(Long orderId, Long merchantId);
    
    boolean startService(Long orderId, Long staffId);
    
    boolean completeService(Long orderId, Long staffId);
    
    boolean cancelOrder(Long orderId, Long userId, String cancelReason);
    
    List<TimeSlotVO> getAvailableTimeSlots(Long merchantId, LocalDate queryDate);
    
    List<MerchantVO> findNearbyMerchants(ServiceQueryDTO queryDTO);
}

// 预约服务实现类
@Service
@Slf4j
public class BookingServiceImpl extends ServiceImpl<ServiceOrderMapper, ServiceOrder> 
    implements BookingService {
    
    @Autowired
    private ServiceMerchantMapper serviceMerchantMapper;
    
    @Autowired
    private ServiceCategoryMapper serviceCategoryMapper;
    
    @Autowired
    private ServiceItemMapper serviceItemMapper;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public OrderCreateResultVO createServiceOrder(OrderCreateDTO createDTO) {
        // 验证订单数据
        validateOrderCreateDTO(createDTO);
        
        // 检查时间是否可用
        if (!isTimeSlotAvailable(createDTO.getMerchantId(), createDTO.getAppointmentTime())) {
            throw new BusinessException("该时间段已被预约,请选择其他时间");
        }
        
        // 创建订单
        ServiceOrder order = new ServiceOrder();
        BeanUtils.copyProperties(createDTO, order);
        order.setOrderNo(generateOrderNo());
        order.setOrderStatus(1); // 待确认
        order.setPayStatus(0); // 未支付
        
        // 计算订单金额
        calculateOrderAmount(order, createDTO);
        
        int result = baseMapper.insert(order);
        if (result > 0) {
            // 锁定预约时间段
            lockTimeSlot(createDTO.getMerchantId(), createDTO.getAppointmentTime(), order.getOrderId());
            
            // 发送新订单通知
            sendNewOrderNotification(order);
            
            OrderCreateResultVO resultVO = new OrderCreateResultVO();
            resultVO.setOrderId(order.getOrderId());
            resultVO.setOrderNo(order.getOrderNo());
            resultVO.setOrderAmount(order.getOrderAmount());
            resultVO.setAppointmentTime(order.getAppointmentTime());
            
            log.info("创建服务订单成功,订单ID:{}", order.getOrderId());
            return resultVO;
        }
        
        throw new BusinessException("创建订单失败");
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean confirmOrder(Long orderId, Long merchantId) {
        ServiceOrder order = baseMapper.selectById(orderId);
        if (order == null) {
            throw new BusinessException("订单不存在");
        }
        
        if (!order.getMerchantId().equals(merchantId)) {
            throw new BusinessException("无权操作此订单");
        }
        
        if (!order.getOrderStatus().equals(1)) {
            throw new BusinessException("订单状态不可确认");
        }
        
        // 更新订单状态
        order.setOrderStatus(2); // 已确认
        order.setUpdateTime(LocalDateTime.now());
        
        int result = baseMapper.updateById(order);
        if (result > 0) {
            // 发送订单确认通知
            sendOrderConfirmedNotification(order);
            return true;
        }
        
        return false;
    }
    
    @Override
    public List<MerchantVO> findNearbyMerchants(ServiceQueryDTO queryDTO) {
        // 从Redis获取附近服务商
        String cacheKey = "nearby_merchants:" + queryDTO.getCategoryId() + ":" + 
                         queryDTO.getLongitude() + ":" + queryDTO.getLatitude();
        List<MerchantVO> cachedResult = (List<MerchantVO>) redisTemplate.opsForValue().get(cacheKey);
        
        if (cachedResult != null) {
            return cachedResult;
        }
        
        // 查询数据库
        LambdaQueryWrapper<ServiceMerchant> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(ServiceMerchant::getAuditStatus, 2) // 已审核
               .like(StringUtils.isNotBlank(queryDTO.getServiceCategories()),
                     ServiceMerchant::getServiceCategories, queryDTO.getServiceCategories())
               .apply("1=1"); // 这里可以添加地理位置查询条件
        
        List<ServiceMerchant> merchants = serviceMerchantMapper.selectList(wrapper);
        
        // 转换为VO并计算距离
        List<MerchantVO> result = merchants.stream()
                .map(merchant -> {
                    MerchantVO vo = new MerchantVO();
                    BeanUtils.copyProperties(merchant, vo);
                    
                    // 计算距离
                    if (queryDTO.getLongitude() != null && queryDTO.getLatitude() != null) {
                        double distance = calculateDistance(
                            queryDTO.getLatitude(), queryDTO.getLongitude(),
                            merchant.getLatitude().doubleValue(), 
                            merchant.getLongitude().doubleValue()
                        );
                        vo.setDistance(distance);
                        vo.setEstimatedTime(calculateEstimatedTime(distance));
                    }
                    
                    // 查询服务项目
                    List<ServiceItem> items = serviceItemMapper.selectByMerchantId(merchant.getMerchantId());
                    vo.setServiceItems(items);
                    
                    return vo;
                })
                .filter(vo -> vo.getDistance() <= 20.0) // 20公里内的服务商
                .sorted(Comparator.comparingDouble(MerchantVO::getDistance))
                .collect(Collectors.toList());
        
        // 缓存结果,5分钟过期
        redisTemplate.opsForValue().set(cacheKey, result, 5, TimeUnit.MINUTES);
        
        return result;
    }
    
    private void validateOrderCreateDTO(OrderCreateDTO createDTO) {
        if (createDTO.getMerchantId() == null) {
            throw new BusinessException("服务商不能为空");
        }
        if (createDTO.getServiceItemId() == null) {
            throw new BusinessException("服务项目不能为空");
        }
        if (createDTO.getAppointmentTime() == null) {
            throw new BusinessException("预约时间不能为空");
        }
        if (StringUtils.isBlank(createDTO.getServiceAddress())) {
            throw new BusinessException("服务地址不能为空");
        }
    }
    
    private boolean isTimeSlotAvailable(Long merchantId, LocalDateTime appointmentTime) {
        // 检查该时间段是否已被预约
        LambdaQueryWrapper<ServiceOrder> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(ServiceOrder::getMerchantId, merchantId)
               .eq(ServiceOrder::getAppointmentTime, appointmentTime)
               .in(ServiceOrder::getOrderStatus, Arrays.asList(1, 2, 3)); // 待确认、已确认、服务中
        
        return baseMapper.selectCount(wrapper) == 0;
    }
    
    private void lockTimeSlot(Long merchantId, LocalDateTime appointmentTime, Long orderId) {
        String lockKey = "time_slot_lock:" + merchantId + ":" + 
                        appointmentTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
        redisTemplate.opsForValue().set(lockKey, orderId, 30, TimeUnit.MINUTES);
    }
    
    private String generateOrderNo() {
        return "LS" + System.currentTimeMillis() + RandomUtil.randomNumbers(4);
    }
    
    private void calculateOrderAmount(ServiceOrder order, OrderCreateDTO createDTO) {
        ServiceItem serviceItem = serviceItemMapper.selectById(createDTO.getServiceItemId());
        if (serviceItem == null) {
            throw new BusinessException("服务项目不存在");
        }
        
        BigDecimal basePrice = serviceItem.getPrice();
        // 这里可以根据服务时长、附加服务等计算总价
        order.setOrderAmount(basePrice);
        order.setPayAmount(basePrice);
    }
    
    private double calculateDistance(double lat1, double lng1, double lat2, double lng2) {
        // 实现距离计算逻辑(Haversine公式)
        double earthRadius = 6371; // 地球半径,单位公里
        double dLat = Math.toRadians(lat2 - lat1);
        double dLng = Math.toRadians(lng2 - lng1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                   Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
                   Math.sin(dLng / 2) * Math.sin(dLng / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return earthRadius * c;
    }
    
    private String calculateEstimatedTime(double distance) {
        if (distance < 1) {
            return "10分钟内";
        } else if (distance < 3) {
            return "20分钟内";
        } else if (distance < 5) {
            return "30分钟内";
        } else {
            return "1小时内";
        }
    }
    
    private void sendNewOrderNotification(ServiceOrder order) {
        // 实现新订单通知逻辑
        // 可以集成短信、微信模板消息等
    }
    
    private void sendOrderConfirmedNotification(ServiceOrder order) {
        // 实现订单确认通知逻辑
    }
}
控制层和API接口设计

RESTful API设计遵循行业标准,提供统一的响应格式和完整的错误处理机制。

控制器代码示例:

复制代码
// 服务预约控制器
@RestController
@RequestMapping("/api/booking")
@Api(tags = "服务预约管理")
@Slf4j
public class BookingController {
    
    @Autowired
    private BookingService bookingService;
    
    @PostMapping("/create")
    @ApiOperation("创建服务订单")
    public Result<OrderCreateResultVO> createServiceOrder(@RequestBody @Valid OrderCreateDTO createDTO) {
        try {
            OrderCreateResultVO result = bookingService.createServiceOrder(createDTO);
            return Result.success("创建订单成功", result);
        } catch (BusinessException e) {
            return Result.error(e.getMessage());
        } catch (Exception e) {
            log.error("创建服务订单失败", e);
            return Result.error("创建订单失败");
        }
    }
    
    @PostMapping("/confirm/{orderId}")
    @ApiOperation("确认订单")
    public Result<String> confirmOrder(@PathVariable Long orderId, @RequestParam Long merchantId) {
        try {
            boolean success = bookingService.confirmOrder(orderId, merchantId);
            return success ? Result.success("确认订单成功") : Result.error("确认订单失败");
        } catch (BusinessException e) {
            return Result.error(e.getMessage());
        } catch (Exception e) {
            log.error("确认订单失败", e);
            return Result.error("确认订单失败");
        }
    }
    
    @PostMapping("/start/{orderId}")
    @ApiOperation("开始服务")
    public Result<String> startService(@PathVariable Long orderId, @RequestParam Long staffId) {
        try {
            boolean success = bookingService.startService(orderId, staffId);
            return success ? Result.success("开始服务成功") : Result.error("开始服务失败");
        } catch (Exception e) {
            log.error("开始服务失败", e);
            return Result.error("开始服务失败");
        }
    }
    
    @PostMapping("/complete/{orderId}")
    @ApiOperation("完成服务")
    public Result<String> completeService(@PathVariable Long orderId, @RequestParam Long staffId) {
        try {
            boolean success = bookingService.completeService(orderId, staffId);
            return success ? Result.success("完成服务成功") : Result.error("完成服务失败");
        } catch (Exception e) {
            log.error("完成服务失败", e);
            return Result.error("完成服务失败");
        }
    }
    
    @PostMapping("/cancel/{orderId}")
    @ApiOperation("取消订单")
    public Result<String> cancelOrder(@PathVariable Long orderId, 
                                     @RequestParam Long userId,
                                     @RequestParam String cancelReason) {
        try {
            boolean success = bookingService.cancelOrder(orderId, userId, cancelReason);
            return success ? Result.success("取消成功") : Result.error("取消失败");
        } catch (BusinessException e) {
            return Result.error(e.getMessage());
        } catch (Exception e) {
            log.error("取消订单失败", e);
            return Result.error("取消失败");
        }
    }
    
    @GetMapping("/nearby-merchants")
    @ApiOperation("查找附近服务商")
    public Result<List<MerchantVO>> findNearbyMerchants(ServiceQueryDTO queryDTO) {
        try {
            List<MerchantVO> merchants = bookingService.findNearbyMerchants(queryDTO);
            return Result.success("查找附近服务商成功", merchants);
        } catch (Exception e) {
            log.error("查找附近服务商失败", e);
            return Result.error("查找附近服务商失败");
        }
    }
    
    @GetMapping("/time-slots/{merchantId}")
    @ApiOperation("获取可预约时间段")
    public Result<List<TimeSlotVO>> getAvailableTimeSlots(@PathVariable Long merchantId,
                                                         @RequestParam String queryDate) {
        try {
            LocalDate date = LocalDate.parse(queryDate);
            List<TimeSlotVO> timeSlots = bookingService.getAvailableTimeSlots(merchantId, date);
            return Result.success("获取可预约时间段成功", timeSlots);
        } catch (Exception e) {
            log.error("获取可预约时间段失败", e);
            return Result.error("获取可预约时间段失败");
        }
    }
}

// 统一响应结果封装
@Data
@ApiModel("统一响应结果")
public class Result<T> implements Serializable {
    
    @ApiModelProperty("状态码")
    private Integer code;
    
    @ApiModelProperty("响应消息")
    private String message;
    
    @ApiModelProperty("响应数据")
    private T data;
    
    @ApiModelProperty("时间戳")
    private Long timestamp;
    
    public static <T> Result<T> success(String message, T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage(message);
        result.setData(data);
        result.setTimestamp(System.currentTimeMillis());
        return result;
    }
    
    public static <T> Result<T> success(String message) {
        return success(message, null);
    }
    
    public static <T> Result<T> error(String message) {
        Result<T> result = new Result<>();
        result.setCode(500);
        result.setMessage(message);
        result.setTimestamp(System.currentTimeMillis());
        return result;
    }
}
前端uniapp用户端实现

用户端采用uniapp开发,支持编译到小程序、公众号、H5等多个平台,为JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统提供一致的用户体验。

Vue组件代码示例:

复制代码
<template>
  <view class="service-booking-container">
    <!-- 服务分类 -->
    <view class="service-category-section">
      <scroll-view scroll-x class="category-scroll">
        <view 
          v-for="category in serviceCategories" 
          :key="category.id"
          class="category-item"
          :class="{ active: selectedCategory === category.id }"
          @click="selectCategory(category.id)"
        >
          <image :src="category.icon" class="category-icon"></image>
          <text class="category-name">{{ category.name }}</text>
        </view>
      </scroll-view>
    </view>

    <!-- 搜索框 -->
    <view class="search-section">
      <u-search
        v-model="searchKeyword"
        placeholder="搜索服务或商家"
        :show-action="true"
        action-text="搜索"
        @search="handleSearch"
        @custom="handleSearch"
      ></u-search>
    </view>

    <!-- 附近服务商 -->
    <view class="merchants-section">
      <view class="section-header">
        <text class="section-title">附近服务商</text>
        <text class="section-more" @click="viewAllMerchants">查看全部</text>
      </view>
      
      <scroll-view scroll-x class="merchant-scroll">
        <view 
          v-for="merchant in nearbyMerchants" 
          :key="merchant.merchantId"
          class="merchant-card"
          @click="viewMerchantDetail(merchant.merchantId)"
        >
          <image :src="merchant.logo" class="merchant-logo" mode="aspectFill"></image>
          <view class="merchant-info">
            <text class="merchant-name">{{ merchant.merchantName }}</text>
            <view class="merchant-rating">
              <u-rate :value="merchant.rating" size="24" readonly></u-rate>
              <text class="rating-text">{{ merchant.rating }}</text>
            </view>
            <view class="merchant-tags">
              <text 
                v-for="tag in merchant.tags" 
                :key="tag"
                class="tag"
              >{{ tag }}</text>
            </view>
            <view class="merchant-distance">
              <u-icon name="map" size="24" color="#999"></u-icon>
              <text class="distance-text">{{ merchant.distance }}km | {{ merchant.estimatedTime }}</text>
            </view>
          </view>
        </view>
      </scroll-view>
    </view>

    <!-- 推荐服务 -->
    <view class="recommended-services">
      <view class="section-header">
        <text class="section-title">推荐服务</text>
      </view>
      
      <view class="service-grid">
        <view 
          v-for="service in recommendedServices" 
          :key="service.id"
          class="service-item"
          @click="bookService(service)"
        >
          <image :src="service.image" class="service-image"></image>
          <view class="service-content">
            <text class="service-name">{{ service.name }}</text>
            <text class="service-desc">{{ service.description }}</text>
            <view class="service-price">
              <text class="price">¥{{ service.price }}</text>
              <text class="unit">起</text>
            </view>
          </view>
        </view>
      </view>
    </view>

    <!-- 底部导航 -->
    <view class="tabbar">
      <view 
        v-for="tab in tabs" 
        :key="tab.key"
        class="tabbar-item"
        :class="{ active: currentTab === tab.key }"
        @click="switchTab(tab.key)"
      >
        <u-icon :name="tab.icon" size="40"></u-icon>
        <text class="tabbar-text">{{ tab.text }}</text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      selectedCategory: 1,
      serviceCategories: [
        { id: 1, name: '家政服务', icon: '/static/icons/cleaning.png' },
        { id: 2, name: '美容美发', icon: '/static/icons/beauty.png' },
        { id: 3, name: '洗车保洁', icon: '/static/icons/car-wash.png' },
        { id: 4, name: '搬家维修', icon: '/static/icons/moving.png' },
        { id: 5, name: '家装服务', icon: '/static/icons/decoration.png' }
      ],
      searchKeyword: '',
      nearbyMerchants: [],
      recommendedServices: [],
      currentTab: 'home',
      tabs: [
        { key: 'home', text: '首页', icon: 'home' },
        { key: 'order', text: '订单', icon: 'order' },
        { key: 'message', text: '消息', icon: 'chat' },
        { key: 'profile', text: '我的', icon: 'account' }
      ]
    }
  },

  onLoad() {
    this.loadNearbyMerchants()
    this.loadRecommendedServices()
  },

  onPullDownRefresh() {
    this.loadNearbyMerchants()
    this.loadRecommendedServices().finally(() => {
      uni.stopPullDownRefresh()
    })
  },

  methods: {
    async loadNearbyMerchants() {
      try {
        const location = await this.getCurrentLocation()
        const params = {
          categoryId: this.selectedCategory,
          longitude: location.longitude,
          latitude: location.latitude,
          pageNum: 1,
          pageSize: 10
        }

        const res = await this.$http.get('/api/booking/nearby-merchants', { params })
        if (res.code === 200) {
          this.nearbyMerchants = res.data
        }
      } catch (error) {
        console.error('加载附近服务商失败:', error)
        uni.showToast({
          title: '加载失败',
          icon: 'none'
        })
      }
    },

    async loadRecommendedServices() {
      try {
        const res = await this.$http.get('/api/service/recommended', {
          params: { categoryId: this.selectedCategory }
        })
        if (res.code === 200) {
          this.recommendedServices = res.data
        }
      } catch (error) {
        console.error('加载推荐服务失败:', error)
      }
    },

    selectCategory(categoryId) {
      this.selectedCategory = categoryId
      this.loadNearbyMerchants()
      this.loadRecommendedServices()
    },

    handleSearch() {
      uni.navigateTo({
        url: `/pages/search/result?keyword=${this.searchKeyword}`
      })
    },

    viewMerchantDetail(merchantId) {
      uni.navigateTo({
        url: `/pages/merchant/detail?id=${merchantId}`
      })
    },

    viewAllMerchants() {
      uni.navigateTo({
        url: `/pages/merchant/list?categoryId=${this.selectedCategory}`
      })
    },

    bookService(service) {
      uni.navigateTo({
        url: `/pages/booking/create?serviceId=${service.id}`
      })
    },

    switchTab(tabKey) {
      this.currentTab = tabKey
      switch (tabKey) {
        case 'order':
          uni.navigateTo({ url: '/pages/order/list' })
          break
        case 'message':
          uni.navigateTo({ url: '/pages/message/list' })
          break
        case 'profile':
          uni.navigateTo({ url: '/pages/profile/index' })
          break
      }
    },

    getCurrentLocation() {
      return new Promise((resolve, reject) => {
        uni.getLocation({
          type: 'gcj02',
          success: (res) => {
            resolve({
              latitude: res.latitude,
              longitude: res.longitude
            })
          },
          fail: (error) => {
            reject(error)
          }
        })
      })
    }
  }
}
</script>

<style scoped>
.service-booking-container {
  padding: 30rpx;
  background-color: #f5f5f5;
  min-height: 100vh;
  padding-bottom: 120rpx;
}

.category-scroll {
  white-space: nowrap;
  margin-bottom: 40rpx;
}

.category-item {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  padding: 20rpx 30rpx;
  background: #fff;
  border-radius: 16rpx;
  margin-right: 20rpx;
  border: 2rpx solid transparent;
}

.category-item.active {
  border-color: #007aff;
  background: #f0f8ff;
}

.category-icon {
  width: 80rpx;
  height: 80rpx;
  margin-bottom: 15rpx;
}

.category-name {
  font-size: 24rpx;
  color: #333;
}

.search-section {
  margin-bottom: 40rpx;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 30rpx;
}

.section-title {
  font-size: 32rpx;
  font-weight: bold;
  color: #333;
}

.section-more {
  font-size: 26rpx;
  color: #999;
}

.merchant-scroll {
  white-space: nowrap;
  margin-bottom: 40rpx;
}

.merchant-card {
  display: inline-flex;
  flex-direction: column;
  width: 280rpx;
  background: #fff;
  border-radius: 16rpx;
  margin-right: 20rpx;
  overflow: hidden;
}

.merchant-logo {
  width: 100%;
  height: 160rpx;
}

.merchant-info {
  padding: 20rpx;
}

.merchant-name {
  font-size: 28rpx;
  font-weight: bold;
  color: #333;
  margin-bottom: 15rpx;
  display: block;
}

.merchant-rating {
  display: flex;
  align-items: center;
  margin-bottom: 15rpx;
}

.rating-text {
  font-size: 24rpx;
  color: #ff6b35;
  margin-left: 10rpx;
}

.merchant-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8rpx;
  margin-bottom: 15rpx;
}

.tag {
  background: #f0f0f0;
  color: #666;
  padding: 6rpx 12rpx;
  border-radius: 12rpx;
  font-size: 20rpx;
}

.merchant-distance {
  display: flex;
  align-items: center;
}

.distance-text {
  font-size: 22rpx;
  color: #999;
  margin-left: 8rpx;
}

.service-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20rpx;
}

.service-item {
  background: #fff;
  border-radius: 16rpx;
  overflow: hidden;
}

.service-image {
  width: 100%;
  height: 180rpx;
}

.service-content {
  padding: 20rpx;
}

.service-name {
  font-size: 28rpx;
  font-weight: bold;
  color: #333;
  display: block;
  margin-bottom: 10rpx;
}

.service-desc {
  font-size: 24rpx;
  color: #666;
  display: block;
  margin-bottom: 15rpx;
}

.service-price {
  display: flex;
  align-items: baseline;
}

.price {
  font-size: 32rpx;
  color: #ff6b35;
  font-weight: bold;
}

.unit {
  font-size: 22rpx;
  color: #999;
  margin-left: 8rpx;
}

.tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  background: #fff;
  border-top: 1rpx solid #f0f0f0;
  padding: 20rpx 0;
}

.tabbar-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.tabbar-item.active {
  color: #007aff;
}

.tabbar-text {
  font-size: 22rpx;
  margin-top: 8rpx;
}
</style>
系统特色与行业价值

JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统通过技术创新和业务整合,为本地生活服务行业提供了全方位的数字化解决方案。这套JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统具备以下核心优势:

技术架构优势:

  • 前后端分离架构,提高开发效率和系统稳定性
  • 多端统一开发,大幅降低维护成本
  • 智能推荐算法,精准匹配用户需求
  • 实时消息推送,确保信息及时送达

业务功能优势:

  • 全品类服务覆盖,满足多样化需求
  • 智能预约系统,优化资源利用率
  • 完善评价体系,保障服务质量
  • 灵活定价策略,适应市场变化

性能与安全:

  • Redis缓存优化,提升系统响应速度
  • 数据库读写分离,支持高并发访问
  • 支付安全加密,保障交易安全
  • 完善的权限控制,保护用户隐私

JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统以其全面的技术栈和丰富的功能特性,为本地生活服务行业提供了真正意义上的一站式数字化解决方案。从后台的springboot+mybatisplus+mysql稳定架构,到用户端的uniapp跨平台实现,再到管理后台的vue+elementUi优雅界面,每一个技术选型都经过精心考量,确保系统在性能、可维护性和用户体验方面达到最佳平衡。

随着本地生活服务市场规模的不断扩大和数字化程度的持续深化,拥有这样一套完整的JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统将成为行业竞争的核心优势。无论是传统家政公司寻求数字化转型,还是新兴平台希望快速占领市场,这套JAVA同城预约服务家政服务美容美发洗车保洁搬家维修家装系统都能提供强有力的技术支撑和业务保障,助力企业在激烈的市场竞争中脱颖而出,推动整个本地生活服务行业向数字化、智能化、标准化方向发展。

相关推荐
红尘客栈23 小时前
Kubernetes 集群调度
java·linux·网络·容器·kubernetes
snow@li3 小时前
d3.js:学习积累
开发语言·前端·javascript
编程岁月3 小时前
java面试-0203-java集合并发修改异常、快速/安全失败原理、解决方法?
java·开发语言·面试
CoderCodingNo4 小时前
【GESP】C++五级考试大纲知识点梳理, (3-4) 链表-双向循环链表
开发语言·c++·链表
whltaoin4 小时前
AI 超级智能体全栈项目阶段五:RAG 四大流程详解、最佳实践与调优(基于 Spring AI 实现)
java·人工智能·spring·rag·springai
junnhwan4 小时前
【苍穹外卖笔记】Day05--Redis入门与店铺营业状态设置
java·数据库·redis·笔记·后端·苍穹外卖
llz_1124 小时前
第五周作业(JavaScript)
开发语言·前端·javascript
摇滚侠4 小时前
Spring Boot 3零基础教程,Spring Boot 特性介绍,笔记02
java·spring boot·笔记
W.Y.B.G4 小时前
JavaScript 计算闰年方法
开发语言·前端·javascript