当前位置: 首页 > news >正文

智慧社区项目开发(五)—— 小区管理模块前后端实现详解:从数据模型到业务逻辑

前端

一、功能概览:小区管理模块的核心能力

小区管理模块主要实现对小区信息的全生命周期管理,包含 5 个核心功能:

  • 分页条件查询:支持按小区名称等条件查询,返回包含小区人数等扩展信息的列表
  • 新增小区:提交小区基本信息(名称、经纬度等)
  • 编辑小区:回显并修改已有小区信息
  • 单条 / 批量删除:删除小区及关联数据(摄像头、居民等)
  • 详情查询:通过 ID 获取小区详情用于编辑回显

二、前端实现:Vue 组件交互与数据流转

前端采用 Vue+Element UI 实现,核心涉及两个组件:Community.vue(列表页)和add-or-update.vue(新增 / 编辑弹窗)。我们重点解析这两个组件的交互逻辑与表单初始化细节。

1. 列表页与弹窗组件的 "显隐控制":不是路由跳转,而是组件切换

很多初学者会疑惑:点击 "添加" 或 "编辑" 按钮时,页面是如何从列表页切换到表单弹窗的?其实这里并没有使用路由跳转,而是通过组件显隐控制实现。

Community.vue(列表页)中,我们定义了addOrUpdateVisible变量控制弹窗显示:

<!-- Community.vue 核心代码 -->
<template><div class="app-container"><!-- 列表与操作按钮 --><el-button @click="addOrUpdateHandle(1)">添加</el-button><el-button @click="addOrUpdateHandle()">编辑</el-button><!-- 新增/编辑弹窗组件:通过v-if控制显隐 --><add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="initData" /></div>
</template><script>
export default {data() {return {addOrUpdateVisible: false // 弹窗显示状态:false隐藏,true显示}},methods: {addOrUpdateHandle(flag) {this.addOrUpdateVisible = true; // 显示弹窗if (flag !== 1) {// 编辑场景:传递选中的小区ID给弹窗this.$nextTick(() => {this.$refs.addOrUpdate.init(this.ids[0].communityId);});} else {// 新增场景:不传递IDthis.$nextTick(() => {this.$refs.addOrUpdate.init();});}}}
}
</script>

核心逻辑

  • 点击 "添加" 或 "编辑" 按钮时,将addOrUpdateVisible设为true,弹窗组件add-or-updatev-if条件满足而渲染
  • 通过this.$refs.addOrUpdate.init(...)调用子组件的init方法,传递小区 ID(编辑时)或空(新增时)
  • 弹窗关闭后,通过@refreshDataList事件通知父组件刷新列表数据

2. 表单初始化方法init(id):为什么先赋值 ID 再重置表单?

add-or-update.vue(弹窗组件)中,init(id)方法是表单初始化的核心,先看代码:

<!-- add-or-update.vue 核心代码 -->
<script>
export default {data() {return {dataForm: {communityId: '',communityName: '',termCount: '',lng: '',lat: '',seq: '1'}}},methods: {init(id) {this.dataForm.communityId = id; // 先给communityId赋值this.visible = true; // 显示弹窗this.resetForm('dataForm'); // 重置表单this.dataForm.seq = 1; // 重置后单独设置seq默认值// 如果是编辑(有id),查询详情并回显if (this.dataForm.communityId) {getInfo(id).then(res => {if (res && res.code === 200) {this.dataForm.communityId = res.data.communityId;this.dataForm.communityName = res.data.communityName;// 其他字段赋值...}});}}}
}
</script>
疑问:先赋值this.dataForm.communityId = id,再调用resetForm('dataForm'),ID 不会被清空吗?

要理解这个问题,需明确两个关键点:

(1)resetForm的作用:清除表单残留数据

resetForm是 Element UI 提供的表单重置方法,作用是将表单字段恢复到初始状态(即data()中定义的初始值)。在dataForm中,communityId的初始值是''(空字符串),所以调用resetForm('dataForm')后,communityId会被重置为''

(2)为什么 ID 最终不会丢失?

这里的关键是条件判断与重新赋值

  • 新增场景:idundefined,执行this.dataForm.communityId = id后值为undefinedresetForm后变为''(不影响,新增无需 ID)
  • 编辑场景:id为具体数值(如 19),执行this.dataForm.communityId = id后值为 19;resetForm后变为'',但随后进入if (this.dataForm.communityId)判断 —— 这里的this.dataForm.communityId看似是'',但实际判断的是id参数(因为resetForm只修改了dataForm,没修改id变量)。

getInfo(id)接口返回数据后,会重新给this.dataForm.communityId赋值(如res.data.communityId = 19),因此最终 ID 会正确回显。

(3)为什么要先赋值再重置?

目的是避免表单残留旧数据。假设用户先编辑了 ID=19 的小区,关闭弹窗后又编辑 ID=20 的小区:如果不重置表单,表单中可能残留 ID=19 的名称、经纬度等数据,导致新数据被污染。resetForm能确保每次编辑都是 "干净的开始",再通过接口获取最新数据回显,保证数据准确性。

后端 

一、基础配置:分页插件与核心依赖

实现分页功能是列表查询的基础,MyBatis-Plus 提供了便捷的分页插件,需先完成配置。

1. 分页插件配置类

package com.qcby.community.configuration;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration // 标识为配置类
public class PageConfiguration {@Bean // 将分页拦截器注入Spring容器public PaginationInterceptor paginationInterceptor(){return new PaginationInterceptor();}
}

作用说明

  • PaginationInterceptor是 MyBatis-Plus 的分页拦截器,会自动拦截带有Page参数的查询方法
  • 配置后,无需手动编写LIMIT语句,框架会自动处理分页逻辑
  • 支持 MySQL、Oracle 等多种数据库,自动适配不同数据库的分页语法

2. 核心依赖(Maven)

<!-- SpringBoot Web -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- MyBatis-Plus -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3.4</version>
</dependency><!-- MySQL驱动 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency><!-- Lombok(简化实体类) -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>

二、数据模型设计:实体类与 VO

后端数据模型需区分数据库实体(Entity) 和视图对象(VO),前者对应数据库表结构,后者用于前端数据展示。

1. 数据库表结构(community 表)

CREATE TABLE `community` (`community_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '小区ID',`community_name` varchar(255) NOT NULL COMMENT '小区名称',`term_count` int(11) DEFAULT NULL COMMENT '楼栋数量',`seq` int(11) DEFAULT NULL COMMENT '排序',`creater` varchar(50) DEFAULT NULL COMMENT '创建人',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`lng` float DEFAULT NULL COMMENT '经度',`lat` float DEFAULT NULL COMMENT '纬度',PRIMARY KEY (`community_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小区表';

2. 实体类(Entity):Community

package com.qcby.community.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;@Data // Lombok注解:自动生成getter、setter、toString等方法
@TableName("community") // 指定对应数据库表名
public class Community {@TableId(type = IdType.AUTO) // 主键自增private Integer communityId;private String communityName;private Integer termCount;private Integer seq;private String creater;private Date createTime;private Float lng;private Float lat;
}

关键点

  • @TableName:当类名与表名不一致时,必须指定表名
  • @TableId:标记主键,type = IdType.AUTO表示数据库自增
  • Lombok 的@Data注解大幅减少模板代码

3. 视图对象(VO):CommunityVO

由于前端列表需要personCnt(小区人数)字段,而该字段不存在于community表中,需定义 VO 用于数据返回:

package com.qcby.community.vo;import lombok.Data;
import java.util.Date;@Data
public class CommunityVO {private Integer communityId;private String communityName;private Integer termCount;private Integer seq;private String creater;private Date createTime;private Float lng;private Float lat;private Integer personCnt; // 小区人数(扩展字段)
}

VO 与 Entity 的区别

  • Entity 严格对应数据库表结构,用于持久化操作
  • VO 根据前端需求定义,包含扩展字段,用于接口返回
  • 通过BeanUtils.copyProperties()实现两者字段的快速复制

三、请求与响应封装

统一请求参数格式和响应格式,是后端接口规范的核心。

1. 请求参数封装:CommunityListForm

前端查询时传递的参数(页码、每页条数、查询条件)需封装为 Form 类:

package com.qcby.community.form;import lombok.Data;@Data
public class CommunityListForm {private Integer page; // 当前页码private Integer limit; // 每页条数private Integer communityId; // 小区ID(可选)private String communityName; // 小区名称(可选,用于模糊查询)
}

2. 分页结果封装:PageVO

分页查询的结果需包含总条数、总页数等信息,封装为PageVO

package com.qcby.community.vo;import lombok.Data;
import java.util.List;@Data
public class PageVO {private Long totalCount; // 总记录数private Long pageSize; // 每页条数private Long totalPage; // 总页数private Long currPage; // 当前页码private List list; // 分页数据列表
}

3. 统一响应格式:Result

所有接口返回统一格式的 JSON,便于前端处理:

package com.qcby.community.common;import lombok.Data;@Data
public class Result {private Integer code; // 状态码:200成功,其他失败private String msg; // 提示信息private Object data; // 响应数据(可选)// 成功响应(无数据)public static Result ok() {Result result = new Result();result.setCode(200);result.setMsg("操作成功");return result;}// 成功响应(带数据)public static Result ok(Object data) {Result result = new Result();result.setCode(200);result.setMsg("操作成功");result.setData(data);return result;}// 失败响应public static Result error(String msg) {Result result = new Result();result.setCode(500);result.setMsg(msg);return result;}// 链式调用:设置数据public Result put(String key, Object value) {// 实际实现可使用Map存储多个数据this.data = value;return this;}
}

四、持久层(Mapper):数据库操作

MyBatis-Plus 的BaseMapper提供了基础 CRUD 方法,复杂查询需自定义 SQL。

1. CommunityMapper

package com.qcby.community.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qcby.community.entity.Community;// 继承BaseMapper<Community>,获得基础CRUD方法
public interface CommunityMapper extends BaseMapper<Community> {
}

BaseMapper 核心方法

  • selectPage(Page<T> page, @Param("ew") Wrapper<T> queryWrapper):分页查询
  • insert(T entity):新增
  • updateById(T entity):根据 ID 更新
  • deleteById(Serializable id):根据 ID 删除
  • selectById(Serializable id):根据 ID 查询

2. PersonMapper(关联查询)

需查询小区对应的居民数量,定义PersonMapper

package com.qcby.community.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qcby.community.entity.Person;
import org.apache.ibatis.annotations.Select;public interface PersonMapper extends BaseMapper<Person> {// 自定义SQL:查询指定小区的居民数量@Select("select count(*) from person where community_id = #{communityId}")Integer getCountByCommunityId(Integer communityId);
}

自定义 SQL 说明

  • 使用@Select注解编写 SQL 语句,适用于简单查询
  • 复杂查询建议使用 XML 映射文件(在 resources/mapper 目录下)
  • #{communityId}是参数占位符,自动防止 SQL 注入

五、业务层(Service):核心逻辑处理

Service 层负责实现业务逻辑,协调多个 Mapper 完成复杂操作。

1. CommunityService 接口

package com.qcby.community.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.qcby.community.entity.Community;
import com.qcby.community.form.CommunityListForm;
import com.qcby.community.vo.PageVO;public interface CommunityService extends IService<Community> {// 分页条件查询小区列表PageVO communityList(CommunityListForm communityListForm);
}

说明:继承IService<Community>,获得 MyBatis-Plus 提供的增强 CRUD 方法。

2. CommunityServiceImpl 实现类

package com.qcby.community.service.impl;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qcby.community.entity.Community;
import com.qcby.community.form.CommunityListForm;
import com.qcby.community.mapper.CommunityMapper;
import com.qcby.community.mapper.PersonMapper;
import com.qcby.community.service.CommunityService;
import com.qcby.community.vo.CommunityVO;
import com.qcby.community.vo.PageVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;@Service
public class CommunityServiceImpl extends ServiceImpl<CommunityMapper, Community> implements CommunityService {@Autowired // 注入PersonMapper,用于查询小区人数private PersonMapper personMapper;@Overridepublic PageVO communityList(CommunityListForm form) {// 1. 构建分页对象(页码从1开始)Page<Community> page = new Page<>(form.getPage(), form.getLimit());// 2. 构建查询条件QueryWrapper<Community> queryWrapper = new QueryWrapper<>();// 模糊查询:如果小区名称不为空,则添加条件queryWrapper.like(StringUtils.isNotBlank(form.getCommunityName()), "community_name", // 数据库字段名form.getCommunityName()); // 查询值// 3. 执行分页查询(MyBatis-Plus自动处理分页)IPage<Community> resultPage = this.baseMapper.selectPage(page, queryWrapper);// 4. 转换为VO列表(补充personCnt字段)List<CommunityVO> voList = new ArrayList<>();for (Community community : resultPage.getRecords()) {CommunityVO vo = new CommunityVO();// 复制基础字段(community到vo)BeanUtils.copyProperties(community, vo);// 查询小区人数并设置到VOvo.setPersonCnt(personMapper.getCountByCommunityId(community.getCommunityId()));voList.add(vo);}// 5. 封装分页结果PageVO pageVO = new PageVO();pageVO.setList(voList);pageVO.setTotalCount(resultPage.getTotal()); // 总记录数pageVO.setPageSize(resultPage.getSize()); // 每页条数pageVO.setCurrPage(resultPage.getCurrent()); // 当前页码pageVO.setTotalPage(resultPage.getPages()); // 总页数return pageVO;}
}

核心逻辑拆解

  1. 分页对象构建Page<Community>指定页码和每页条数
  2. 查询条件组装QueryWrapper用于动态拼接 SQL 条件(如模糊查询)
  3. 分页查询执行selectPage方法返回包含分页信息的IPage对象
  4. 数据转换:将Community列表转换为CommunityVO列表,补充扩展字段
  5. 结果封装:将分页信息(总条数、总页数等)封装到PageVO

六、控制层(Controller):接口暴露

Controller 层负责接收前端请求,调用 Service 处理,并返回响应结果。

package com.qcby.community.controller;import com.qcby.community.form.CommunityListForm;
import com.qcby.community.common.Result;
import com.qcby.community.service.CommunityService;
import com.qcby.community.vo.PageVO;
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.RestController;@RestController // 标识为控制器,且返回JSON
@RequestMapping("/community") // 基础路径
public class CommunityController {@Autowiredprivate CommunityService communityService;/*** 分页条件查询小区列表* GET请求:/community/list*/@GetMapping("/list")public Result getList(CommunityListForm form) {PageVO pageVO = communityService.communityList(form);return Result.ok().put("data", pageVO);}/*** 添加小区* POST请求:/community/add*/@PostMapping("/add")public Result add(@RequestBody Community community, HttpSession session) {// 从session获取当前登录用户(简化示例)User user = (User) session.getAttribute("user");community.setCreater(user.getUsername());community.setCreateTime(new Date()); // 设置创建时间boolean success = communityService.save(community);return success ? Result.ok() : Result.error("添加失败");}/*** 根据ID查询小区(编辑回显)* GET请求:/community/info/{id}*/@GetMapping("/info/{id}")public Result info(@PathVariable Integer id) {Community community = communityService.getById(id);return community != null ? Result.ok().put("data", community) : Result.error("小区不存在");}/*** 修改小区* PUT请求:/community/edit*/@PutMapping("/edit")public Result edit(@RequestBody Community community) {boolean success = communityService.updateById(community);return success ? Result.ok() : Result.error("修改失败");}/*** 批量删除小区* DELETE请求:/community/del*/@DeleteMapping("/del")@Transactional // 事务管理:确保关联表数据同时删除public Result del(@RequestBody Integer[] ids) {try {// 1. 删除关联表数据(摄像头、居民等)cameraService.remove(new QueryWrapper<Camera>().in("community_id", ids));personService.remove(new QueryWrapper<Person>().in("community_id", ids));// 2. 删除小区表数据communityService.removeByIds(Arrays.asList(ids));return Result.ok();} catch (Exception e) {e.printStackTrace();return Result.error("删除失败");}}
}

Controller 关键注解

  • @RestController:组合@Controller@ResponseBody,返回 JSON
  • @RequestMapping:指定基础请求路径
  • @GetMapping/@PostMapping等:指定 HTTP 请求方法
  • @PathVariable:获取 URL 路径中的参数
  • @RequestBody:接收 JSON 格式的请求体
  • @Transactional:声明事务,确保操作的原子性

 

 

http://www.dtcms.com/a/310229.html

相关文章:

  • vue+element 实现下拉框共享options
  • Js引用数据类型和ES6新特性
  • 幂等性校验(订单重复提交问题)
  • 生物医药研究数据分析工具测评:衍因科技如何重塑科研范式?
  • 鸿蒙 ArkWeb 加载优化方案详解(2025 最佳实践)
  • Linux文件操作:从C接口到系统调用
  • 8.1IO进程线程——文件IO函数
  • S7-1200 /1500 PLC 进阶技巧:组织块(OB1、OB10)理论到实战
  • 代码随想录day52图论3
  • ReAct模式深度解析:构建具备推理能力的AI智能体架构
  • 日志归档存储策略在海外云服务器环境的容量规划方法
  • 2508C++,奇怪的保留值
  • Qt deleteLater 延迟删除原理
  • 逻辑回归召回率优化方案
  • 第15讲——微分方程
  • 云服务器涉及的应用场景
  • 将本地commit已经push到orgin后如何操作
  • 应用Builder模式在C++中进行复杂对象构建
  • 梦幻接球 - 柔和色彩反弹小游戏
  • c#保留小数点后几位 和 保留有效数字
  • ctfshow_web签到题
  • LS-DYNA 分析任务耗时长,企业如何科学提升许可证使用效率?
  • 编程算法:驱动技术创新与业务增长
  • 丝杆支撑座在电子装配中的关键作用
  • 退出python的base环境
  • 基于STM32的数控机床物联网改造研究
  • 大模型应用
  • Flowable BPMN:智能流程自动化技术全面指南
  • Linux基础服务(DNS和DHCP)
  • 安卓开发--RelativeLayout(相对布局)