【Uni-App+SSM 宠物项目实战】Day14:商家服务列表

76 阅读13分钟

一、前言

欢迎回到mypet项目实战!📋 今天我们实现商家服务管理核心功能—— 商家服务列表展示与上拉加载更多。商家成功注册并通过审核后,可发布宠物服务项目(如洗澡、美容、医疗等),用户通过服务列表浏览并预约。

本次实现的核心是 “分页加载” 技术:后端使用 MyBatis-Plus 的Page对象实现分页查询,前端集成Mescroll-Uni组件实现上拉加载更多(类似电商 APP 的商品列表)。即使是零基础,也能通过 “复制代码 + 注释解析” 掌握分页逻辑与滚动加载的实现。

📌 学习目标

  1. 掌握 MP 的Page分页查询,实现按商家 ID、服务状态等条件的分页数据返回;
  1. 熟练使用Mescroll-Uni组件,实现上拉加载更多、下拉刷新功能;
  1. 理解 “分页参数传递”“数据追加”“加载状态管理” 等关键逻辑;
  1. 解决 “数据重复加载”“无更多数据判断”“下拉刷新重置” 等实战问题。

二、前置准备

开始编码前,请确认以下内容已就绪:

项目检查内容注意事项
数据库表结构fuwuxiangmu(服务项目表)需包含以下字段:id(主键)、shangjia_id(关联商家 ID)、fuwumingcheng(服务名称)、fuwujiage(服务价格)、fuwudescribe(服务描述)、zhuangtai(状态:0 - 下架,1 - 上架)、addtime(添加时间)若表 / 字段缺失,执行建表 SQL:sqlCREATE TABLE fuwuxiangmu (id BIGINT PRIMARY KEY AUTO_INCREMENT, shangjia_id BIGINT NOT NULL, fuwumingcheng VARCHAR(100) NOT NULL, fuwujiage DECIMAL(10,2) NOT NULL, zhuangtai TINYINT DEFAULT 1 COMMENT '0-下架,1-上架');
后端配置1. 已配置 MP 分页插件(MybatisPlusConfig中注册PaginationInterceptor);2. 商家已通过审核(shangjia表zhuangtai=1)若未配置分页插件,需在MybatisPlusConfig中添加:@Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); }
前端组件1. 已导入Mescroll-Uni组件(HBuilder X→插件市场搜索安装);2. pages.json配置路由: "pages": [{"path": "pages/fuwuxiangmu/list","style": {"navigationBarTitleText": "我的服务列表"}}]确保Mescroll-Uni组件路径正确(默认/components/mescroll-uni/mescroll-uni.vue)
测试数据往fuwuxiangmu表插入测试数据(关联已通过审核的商家 ID):INSERT INTO fuwuxiangmu (shangjia_id, fuwumingcheng, fuwujiage, zhuangtai) VALUES (1, '宠物洗澡', 39.90, 1), (1, '毛发修剪', 69.90, 1);确保shangjia_id对应的数据在shangjia表中存在且zhuangtai=1

三、服务列表分页加载流程图

先通过流程图理清 “初始加载→上拉加载→下拉刷新” 的完整逻辑:

flowchart TD
    A[用户进入服务列表页] --> B[页面初始化<br>调用Mescroll的init方法]
    B --> C[默认触发下拉刷新<br>首次加载等同于刷新]
    C --> D[重置分页参数<br>page=1,list=空,hasMore=true]
    D --> E[调用后端/page接口<br>传递page=1,size=10,shangjia_id=当前商家ID]
    E --> F{后端返回数据}
    F -- 否 --> G[显示暂无服务数据]
    F -- 是 --> H[前端list接收第一页数据<br>res.data.records]
    H --> I[Mescroll结束刷新<br>显示第一页服务列表]
    I --> J[用户上拉页面至底部]
    J --> K{Mescroll检测到上拉<br>且hasMore=true}
    K -- 否 --> L[显示没有更多数据了]
    K -- 是 --> M[page+1 page=2<br>调用后端/page接口]
    M --> N{第二页有数据}
    N -- 是 --> O[list拼接新数据<br>list = list.concat新数据]
    N -- 否 --> P[设置hasMore=false<br>不再触发上拉加载]
    O/P --> Q[Mescroll结束加载<br>更新列表显示]
    Q --> R[用户下拉页面<br>触发下拉刷新]
    R --> D[重置分页参数<br>重新加载第一页]

四、代码实现

4.1 后端:分页查询接口开发(按商家 ID 过滤)

4.1.1 1. FuwuxiangmuController:服务列表分页查询

路径:src/main/java/com/controller/FuwuxiangmuController.java

核心功能:接收分页参数 + 当前商家 ID→按条件分页查询服务列表→返回分页数据。

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.entity.FuwuxiangmuEntity;
import com.entity.ShangjiaEntity;
import com.service.FuwuxiangmuService;
import com.service.ShangjiaService;
import com.utils.R;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/fuwuxiangmu")
public class FuwuxiangmuController {
    @Autowired
    private FuwuxiangmuService fuwuxiangmuService;
    @Autowired
    private ShangjiaService shangjiaService;
    // JWT密钥(与Day10一致)
    private static final String JWT_SECRET = "mypet-secret-2024";
    /**
     * 商家服务列表分页查询(仅查询当前商家的服务,且支持按状态筛选)
     * @param current 页码(从1开始)
     * @param size 每页条数
     * @param zhuangtai 服务状态(可选:0-下架,1-上架,null-全部)
     * @param request 用于获取Token,解析用户ID→查询关联的商家ID
     */
    @GetMapping("/page")
    public R getServicePage(
            @RequestParam(defaultValue = "1") long current,  // 默认第一页
            @RequestParam(defaultValue = "10") long size,    // 默认每页10条
            @RequestParam(required = false) Integer zhuangtai,  // 可选参数:状态筛选
            HttpServletRequest request
    ) {
        //  从Token解析当前登录用户ID→查询该用户关联的商家ID
        String token = request.getHeader("token");
        Claims claims = Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token).getBody();
        Long userId = Long.parseLong(claims.getSubject());
        // 查询用户关联的商家(一个用户只能注册一个商家)
        ShangjiaEntity shangjia = shangjiaService.getOne(
            new QueryWrapper<ShangjiaEntity>().eq("user_id", userId)
        );
        if (shangjia == null) {
            return R.error("您还未注册商家,无法查看服务列表!");
        }
        Long shangjiaId = shangjia.getId();  // 当前商家ID
        // 构建MP分页对象(current页码,size每页条数)
        Page<FuwuxiangmuEntity> page = new Page<>(current, size);
        //  构建查询条件(仅查询当前商家的服务,支持按状态筛选)
        QueryWrapper<FuwuxiangmuEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("shangjia_id", shangjiaId);  // 仅显示当前商家的服务
        if (zhuangtai != null) {
            queryWrapper.eq("zhuangtai", zhuangtai);  // 可选:按状态筛选(上架/下架)
        }
        queryWrapper.orderByDesc("addtime");  // 按添加时间倒序(最新的在前面)
        // MP的page()方法:执行分页查询
        IPage<FuwuxiangmuEntity> servicePage = fuwuxiangmuService.page(page, queryWrapper);
        //  返回分页数据(包含总条数、总页数、当前页数据等)
        return R.ok().put("page", servicePage);
    }
}

📌 后端关键讲解

  • 数据权限控制:通过shangjia_id过滤,确保商家只能看到自己发布的服务,避免越权查看他人服务;
  • 灵活筛选:支持传入zhuangtai参数筛选 “上架 / 下架” 服务(后续可在前端添加筛选按钮);
  • 分页参数默认值:current默认 1、size默认 10,避免前端未传参数时的空指针异常;
  • 排序逻辑:按addtime倒序,确保最新发布的服务显示在前面,提升用户体验。

4.1.2 2. Service 层:无需自定义实现(MP 原生方法)

路径(Service):src/main/java/com/service/FuwuxiangmuService.java

路径(ServiceImpl):src/main/java/com/service/impl/FuwuxiangmuServiceImpl.java

Service 接口

import com.entity.FuwuxiangmuEntity;
import com.baomidou.mybatisplus.extension.service.IService;
public interface FuwuxiangmuService extends IService<FuwuxiangmuEntity> {
    // 无需自定义方法,MP的IService已包含page()(分页查询)等
}

ServiceImpl 实现类

import com.entity.FuwuxiangmuEntity;
import com.mapper.FuwuxiangmuMapper;
import com.service.FuwuxiangmuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class FuwuxiangmuServiceImpl extends ServiceImpl<FuwuxiangmuMapper, FuwuxiangmuEntity> implements FuwuxiangmuService {
    // 无需重写page(),父类已实现分页查询逻辑
}

4.2 前端:服务列表 + Mescroll 上拉加载

路径:pages/fuwuxiangmu/list.vue

核心功能:初始化 Mescroll 组件、上拉加载更多、下拉刷新、服务列表展示。

<template>
  <view class="service-list-page">
    <!-- Mescroll-Uni组件(上拉加载+下拉刷新) -->
    <mescroll-uni 
      ref="mescrollRef" 
      :down="downOption" 
      :up="upOption" 
      @down="onDownRefresh" 
      @up="onUpLoad"
      class="mescroll"
    >
      <!-- 服务列表 -->
      <view class="service-list">
        <!-- 服务项 -->
        <view class="service-item" v-for="(item, index) in serviceList" :key="item.id">
          <view class="service-name">
            {{ item.fuwumingcheng }}
            <view class="status-tag" :class="item.zhuangtai === 1 ? 'status-on' : 'status-off'">
              {{ item.zhuangtai === 1 ? '已上架' : '已下架' }}
            </view>
          </view>
          <view class="service-price">¥{{ item.fuwujiage.toFixed(2) }}</view>
          <view class="service-desc">{{ item.fuwudescribe || '暂无描述' }}</view>
          <view class="service-opt">
            <button @click="handleEdit(item)">编辑</button>
            <button @click="handleChangeStatus(item)">
              {{ item.zhuangtai === 1 ? '下架' : '上架' }}
            </button>
          </view>
        </view>
        <!-- 空状态(无服务数据时显示) -->
        <view class="empty-view" v-if="serviceList.length === 0 && !isLoading">
          <image src="/static/images/empty_service.png" mode="widthFix" class="empty-img"></image>
          <text class="empty-text">暂无服务数据,点击添加服务吧~</text>
          <button class="add-btn" @click="navToAdd">添加服务</button>
        </view>
      </view>
    </mescroll-uni>
    <!-- 添加服务按钮(悬浮在右下角) -->
    <button class="float-add-btn" @click="navToAdd">+</button>
  </view>
</template>
<script>
import MescrollUni from '@/components/mescroll-uni/mescroll-uni.vue';
import request from '@/api/request.js';
export default {
  components: { MescrollUni },
  data() {
    return {
      // 服务列表数据
      serviceList: [],
      // 分页参数
      pageNum: 1,       // 当前页码(从1开始)
      pageSize: 10,     // 每页条数
      hasMore: true,    // 是否还有更多数据(控制上拉加载)
      isLoading: false, // 是否正在加载中(防止重复请求)
      // Mescroll下拉刷新配置
      downOption: {
        use: true,          // 启用下拉刷新
        auto: true,         // 页面初始化时自动执行一次下拉刷新
        offset: 60          // 下拉刷新的触发距离(px)
      },
      // Mescroll上拉加载配置
      upOption: {
        use: true,          // 启用上拉加载
        auto: false,        // 不自动执行上拉加载(首次加载由下拉刷新完成)
        page: {
          num: 0,           // 初始页码(0代表不需要组件内部管理页码,由我们自己管理)
          size: 10          // 每页条数(与pageSize保持一致)
        },
        noMoreSize: 5,      // 当剩余数据不足5条时,视为无更多数据
        offset: 100         // 上拉加载的触发距离(px)
      }
    };
  },
  onLoad() {
    // 初始化Mescroll(可选,用于手动控制)
    this.$nextTick(() => {
      this.mescroll = this.$refs.mescrollRef;
    });
  },
  methods: {
    // 1. 下拉刷新(重新加载第一页数据)
    onDownRefresh() {
      // 重置分页参数
      this.pageNum = 1;
      this.hasMore = true;
      this.isLoading = true;
      // 调用接口加载数据
      this.loadServiceList()
        .then(() => {
          // 刷新成功:结束下拉刷新动画
          this.mescroll.endSuccess();
        })
        .catch(() => {
          // 刷新失败:结束下拉刷新动画(显示错误状态)
          this.mescroll.endErr();
        })
        .finally(() => {
          this.isLoading = false;
        });
    },
    // 2. 上拉加载更多(加载下一页数据)
    onUpLoad(mescroll) {
      // 如果没有更多数据,直接结束加载
      if (!this.hasMore) {
        mescroll.endNoMore();
        return;
      }
      this.isLoading = true;
      // 页码+1,加载下一页
      this.pageNum++;
      // 调用接口加载数据
      this.loadServiceList()
        .then((hasData) => {
          if (hasData) {
            // 有新数据:结束加载,更新列表
            mescroll.endSuccess(hasData.length);
          } else {
            // 无新数据:标记无更多,结束加载
            this.hasMore = false;
            mescroll.endNoMore();
          }
        })
        .catch(() => {
          // 加载失败:恢复页码,结束加载(允许重试)
          this.pageNum--;
          mescroll.endErr();
        })
        .finally(() => {
          this.isLoading = false;
        });
    },
    // 3. 加载服务列表数据(通用方法,供下拉刷新和上拉加载调用)
    loadServiceList() {
      return new Promise((resolve, reject) => {
        // 调用后端分页接口(传递页码、每页条数、可选状态筛选)
        request.get(`/fuwuxiangmu/page?current=${this.pageNum}&size=${this.pageSize}`)
          .then(res => {
            if (res.data.code === 0) {
              const pageData = res.data.page;
              const records = pageData.records || []; // 当前页数据
              if (this.pageNum === 1) {
                // 第一页:直接覆盖列表(下拉刷新场景)
                this.serviceList = records;
              } else {
                // 非第一页:拼接列表(上拉加载场景)
                this.serviceList = this.serviceList.concat(records);
              }
              // 判断是否还有更多数据(当前页数据条数 < 每页条数 → 无更多)
              const hasMoreData = records.length >= this.pageSize;
              this.hasMore = hasMoreData;
              resolve(records); // 返回当前页数据,供上拉加载判断
            } else {
              // 接口返回错误(如未注册商家)
              uni.showToast({ title: res.data.msg, icon: 'none' });
              reject(res.data.msg);
            }
          })
          .catch(err => {
            // 网络错误
            uni.showToast({ title: '加载服务列表失败', icon: 'none' });
            console.error('服务列表加载失败:', err);
            reject(err);
          });
      });
    },
    // 4. 跳转到添加服务页面
    navToAdd() {
      uni.navigateTo({ url: '/pages/fuwuxiangmu/add' });
    },
    // 5. 编辑服务(后续实现)
    handleEdit(item) {
      uni.navigateTo({ url: `/pages/fuwuxiangmu/edit?id=${item.id}` });
    },
    // 6. 更改服务状态(上架/下架,后续实现)
    handleChangeStatus(item) {
      // 后续实现状态切换逻辑
      const newStatus = item.zhuangtai === 1 ? 0 : 1;
      uni.showModal({
        title: '提示',
        content: `确定要${newStatus === 1 ? '上架' : '下架'}该服务吗?`,
        success: (modalRes) => {
          if (modalRes.confirm) {
            // 调用状态更新接口(此处仅为示例,后续实现)
            item.zhuangtai = newStatus;
            uni.showToast({ title: `已${newStatus === 1 ? '上架' : '下架'}`, icon: 'success' });
          }
        }
      });
    }
  }
};
</script>
<style scoped>
/* 页面整体样式 */
.service-list-page {
  width: 100%;
  min-height: 100vh;
  background-color: #f5f5f5;
}
/* Mescroll容器样式(必须设置高度) */
.mescroll {
  width: 100%;
  min-height: calc(100vh - 0rpx); /* 占满屏幕高度 */
  padding-bottom: 120rpx; /* 预留底部添加按钮空间 */
}
/* 服务列表样式 */
.service-list {
  padding: 20rpx;
}
/* 服务项样式 */
.service-item {
  background-color: #fff;
  border-radius: 16rpx;
  padding: 30rpx;
  margin-bottom: 20rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
/* 服务名称与状态 */
.service-name {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 32rpx;
  font-weight: bold;
  color: #333;
  margin-bottom: 20rpx;
}
.status-tag {
  padding: 4rpx 16rpx;
  border-radius: 20rpx;
  font-size: 24rpx;
  font-weight: normal;
}
.status-on {
  background-color: #e6f7ee;
  color: #00b42a;
}
.status-off {
  background-color: #fff2e8;
  color: #ff7d00;
}
/* 服务价格 */
.service-price {
  font-size: 30rpx;
  color: #f53f3f;
  margin-bottom: 16rpx;
}
/* 服务描述 */
.service-desc {
  font-size: 26rpx;
  color: #666;
  line-height: 1.5;
  margin-bottom: 24rpx;
  display: -webkit-box;
  -webkit-line-clamp: 2; /* 最多显示2行 */
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* 操作按钮 */
.service-opt {
  display: flex;
  justify-content: flex-end;
  gap: 20rpx;
}
.service-opt button {
  padding: 8rpx 24rpx;
  font-size: 26rpx;
  border-radius: 8rpx;
}
.service-opt button:first-child {
  background-color: #f2f3f5;
  color: #333;
}
.service-opt button:last-child {
  background-color: #007aff;
  color: #fff;
}
/* 空状态样式 */
.empty-view {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 200rpx;
}
.empty-img {
  width: 300rpx;
  margin-bottom: 40rpx;
}
.empty-text {
  font-size: 28rpx;
  color: #999;
  margin-bottom: 40rpx;
}
.add-btn {
  padding: 20rpx 60rpx;
  background-color: #007aff;
  color: #fff;
  font-size: 28rpx;
  border-radius: 50rpx;
}
/* 悬浮添加按钮 */
.float-add-btn {
  position: fixed;
  right: 40rpx;
  bottom: 40rpx;
  width: 100rpx;
  height: 100rpx;
  border-radius: 50%;
  background-color: #007aff;
  color: #fff;
  font-size: 60rpx;
  line-height: 100rpx;
  text-align: center;
  box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3);
  padding: 0;
}
</style>

📌 前端关键细节讲解

  1. Mescroll 配置优化
    • 下拉刷新auto: true:页面初始化时自动加载第一页数据,无需用户手动下拉;
    • 上拉加载page.num: 0:禁用组件内部页码管理,由前端手动控制pageNum,避免页码混乱;
    • noMoreSize: 5:当剩余数据不足 5 条时视为无更多,提前显示 “没有更多数据”,提升体验。
  1. 分页逻辑控制
    • 下拉刷新:重置pageNum=1,覆盖serviceList,实现 “重新加载”;
    • 上拉加载:pageNum++,用concat拼接新数据,实现 “加载更多”;
    • hasMore判断:通过 “当前页数据条数 < 每页条数” 判断是否还有更多,避免无效请求。
  1. 用户体验优化
    • 空状态处理:无数据时显示引导图片和 “添加服务” 按钮,减少用户困惑;
    • 加载状态管理:isLoading防止重复请求,按钮禁用状态随加载状态变化;
    • 服务状态可视化:用不同颜色标签区分 “已上架”“已下架”,直观清晰。

五、效果验证

按以下步骤验证服务列表与分页加载功能:

✅ 1. 后端接口测试(Postman)

  1. 请求地址http://localhost:8080/fuwuxiangmu/page?current=1&size=10
  1. 请求方式:GET
  1. 请求头:token: 有效商家用户Token(该用户已注册并通过商家审核)
  1. 成功返回
{
  "code": 0,
  "msg": "success",
  "page": {
    "records": [
      {
        "id": 1,
        "shangjia_id": 1,
        "fuwumingcheng": "宠物洗澡",
        "fuwujiage": 39.90,
        "zhuangtai": 1
      },
      {
        "id": 2,
        "shangjia_id": 1,
        "fuwumingcheng": "毛发修剪",
        "fuwujiage": 69.90,
        "zhuangtai": 1
      }
    ],
    "total": 2,       // 总条数
    "size": 10,       // 每页条数
    "current": 1,     // 当前页码
    "pages": 1        // 总页数
  }
}

✅ 2. 前端功能测试(Uni-App 模拟器 / 真机)

  1. 登录与跳转:使用已通过审核的商家账号登录→跳转至服务列表页;
  1. 初始加载:页面自动触发下拉刷新→显示第一页服务数据(如 “宠物洗澡”“毛发修剪”);
  1. 上拉加载
    • 若服务数据超过 10 条(可手动在数据库添加),上拉页面至底部→自动加载第二页数据,列表追加显示;
    • 若数据不足 10 条(如仅 2 条),上拉后显示 “没有更多数据了”;
  1. 下拉刷新:下拉页面→列表清空并重新加载第一页数据,恢复初始状态;
  1. 空状态验证:删除fuwuxiangmu表中该商家的所有数据→页面显示空状态图片和 “添加服务” 按钮。

六、常见问题与排查

问题现象可能原因解决方式
1. 上拉加载重复请求同一页1. pageNum未正确递增;2. hasMore未设为 false1. 检查onUpLoad中是否执行this.pageNum++;2. 确认hasMore在 “当前页数据 < pageSize” 时设为 false;3. 打印pageNum,确保每次上拉 + 1
2. 下拉刷新后数据未更新1. 未重置pageNum=1;2. 未覆盖serviceList1. 检查onDownRefresh中是否重置this.pageNum=1;2. 确认第一页数据用this.serviceList = records(覆盖而非拼接);3. 打印serviceList,确认刷新后为最新数据
3. Mescroll 不触发上拉加载1. 组件高度不足(未占满屏幕);2. up.use=false1. 确保.mescroll样式设置min-height: 100vh;2. 检查upOption.use是否为 true;3. 上拉时超过offset设置的距离(如 100px)
4. 服务列表显示其他商家数据后端未添加shangjia_id条件过滤1. 检查queryWrapper.eq("shangjia_id", shangjiaId)是否执行;2. 打印shangjiaId,确认与当前商家匹配;3. 数据库查询验证,确保fuwuxiangmu表shangjia_id正确
5. 无更多数据仍显示加载中mescroll.endNoMore()未调用1. 检查 “无新数据” 分支是否执行mescroll.endNoMore();2. 确认hasMore设为 false 后,不再触发上拉加载;3. 打印records.length,确认小于pageSize时进入无更多分支

七、扩展与提升

7.1 功能优化:服务状态筛选与搜索

当前仅显示所有服务,可扩展筛选功能:

  1. 前端添加 “全部 / 已上架 / 已下架” 筛选按钮,点击后传递zhuangtai参数重新加载;
  1. 添加搜索框,支持按服务名称模糊搜索(后端queryWrapper.like("fuwumingcheng", keyword));
  1. 筛选 / 搜索后自动重置分页参数(pageNum=1),避免页码混乱。

7.2 体验优化:预加载与骨架屏

减少用户等待感:

  1. 实现预加载:当用户上拉至距离底部 200px 时,提前加载下一页数据;
  1. 添加骨架屏:数据加载过程中显示服务项骨架屏(用uni-skeleton组件),替代空白页面。

7.3 性能优化:列表项复用

长列表优化:

  1. 使用uni-recycle-view替代普通v-for,实现列表项 DOM 复用,减少内存占用;
  1. 图片懒加载:服务项若有图片,添加lazy-load属性,滚动到可视区域再加载。

八、课堂互动

🙋‍♂️ 思考题 / 互动:

  1. 如何实现 “下拉刷新时显示最新数据,且不重复加载已删除的服务”?(提示:依赖后端数据更新)
  1. 若服务列表包含图片,如何优化上拉加载时的图片加载性能?

💡 互动引导:你的服务列表和上拉加载功能正常吗?如果遇到 “数据重复” 或 “Mescroll 不触发” 的问题,欢迎分享你的loadServiceList代码或Mescroll配置,我们一起排查!

九、下节预告

👉 明天 Day15:添加与编辑商家服务!我们将学习:

  1. 实现 “添加服务” 表单(服务名称、价格、描述等字段);
  1. 开发 “编辑服务” 功能(回显数据 + 修改提交);
  1. 服务状态切换接口(上架 / 下架);
  1. 前端表单校验与后端参数验证的双重保障。

记得提前复习表单提交(Day12)和 MP 的saveOrUpdate方法哦!