代码6:
INDEX idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源评价表';
-- 评价回复表
CREATE TABLE review_reply (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '回复ID',
review_id BIGINT NOT NULL COMMENT '评价ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
content TEXT NOT NULL COMMENT '回复内容',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_review_id (review_id),
INDEX idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评价回复表';
2.1 Entity Classes
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\entity\Appointment.java
java
CopyInsert
package com.rental.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 预约看房实体类
*/
@Data
public class Appointment implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 预约ID
*/
private Long id;
/**
* 房源ID
*/
private Long houseId;
/**
* 用户ID
*/
private Long userId;
/**
* 房东ID
*/
private Long landlordId;
/**
* 预约时间
*/
private Date appointmentTime;
/**
* 联系人姓名
*/
private String contactName;
/**
* 联系电话
*/
private String contactPhone;
/**
* 预约备注
*/
private String appointmentNotes;
/**
* 状态:0-待确认,1-已确认,2-已取消,3-已完成
*/
private Integer status;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\entity\ViewingFeedback.java
java
CopyInsert
package com.rental.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 看房反馈实体类
*/
@Data
public class ViewingFeedback implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 反馈ID
*/
private Long id;
/**
* 预约ID
*/
private Long appointmentId;
/**
* 用户ID
*/
private Long userId;
/**
* 房源ID
*/
private Long houseId;
/**
* 反馈内容
*/
private String feedbackContent;
/**
* 满意度:1-5星
*/
private Integer satisfactionLevel;
/**
* 是否公开:0-私密,1-公开
*/
private Integer isPublic;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\entity\HouseReview.java
java
CopyInsert
package com.rental.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 房源评价实体类
*/
@Data
public class HouseReview implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 评价ID
*/
private Long id;
/**
* 房源ID
*/
private Long houseId;
/**
* 用户ID
*/
private Long userId;
/**
* 评价内容
*/
private String content;
/**
* 评分:1-5星
*/
private Integer rating;
/**
* 位置评分:1-5星
*/
private Integer locationRating;
/**
* 清洁度评分:1-5星
*/
private Integer cleanlinessRating;
/**
* 性价比评分:1-5星
*/
private Integer valueRating;
/**
* 房东评分:1-5星
*/
private Integer landlordRating;
/**
* 评价图片,多个图片用逗号分隔
*/
private String images;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\entity\ReviewReply.java
java
CopyInsert
package com.rental.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 评价回复实体类
*/
@Data
public class ReviewReply implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 回复ID
*/
private Long id;
/**
* 评价ID
*/
private Long reviewId;
/**
* 用户ID
*/
private Long userId;
/**
* 回复内容
*/
private String content;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
2.2 DTO Classes
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\dto\AppointmentDTO.java
java
CopyInsert
package com.rental.dto;
import lombok.Data;
import javax.validation.constraints.Future;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.util.Date;
/**
* 预约DTO
*/
@Data
public class AppointmentDTO {
/**
* 房源ID
*/
@NotNull(message = "房源ID不能为空")
private Long houseId;
/**
* 预约时间
*/
@NotNull(message = "预约时间不能为空")
@Future(message = "预约时间必须是将来的时间")
private Date appointmentTime;
/**
* 联系人姓名
*/
@NotBlank(message = "联系人姓名不能为空")
private String contactName;
/**
* 联系电话
*/
@NotBlank(message = "联系电话不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码格式不正确")
private String contactPhone;
/**
* 预约备注
*/
private String appointmentNotes;
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\dto\FeedbackDTO.java
java
CopyInsert
package com.rental.dto;
import lombok.Data;
import javax.validation.constraints.*;
/**
* 看房反馈DTO
*/
@Data
public class FeedbackDTO {
/**
* 预约ID
*/
@NotNull(message = "预约ID不能为空")
private Long appointmentId;
/**
* 反馈内容
*/
@NotBlank(message = "反馈内容不能为空")
@Size(min = 10, max = 500, message = "反馈内容长度必须在10-500个字符之间")
private String feedbackContent;
/**
* 满意度:1-5星
*/
@NotNull(message = "满意度不能为空")
@Min(value = 1, message = "满意度最小为1星")
@Max(value = 5, message = "满意度最大为5星")
private Integer satisfactionLevel;
/**
* 是否公开:0-私密,1-公开
*/
@NotNull(message = "是否公开不能为空")
private Integer isPublic;
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\dto\ReviewDTO.java
java
CopyInsert
package com.rental.dto;
import lombok.Data;
import javax.validation.constraints.*;
import java.util.List;
/**
* 房源评价DTO
*/
@Data
public class ReviewDTO {
/**
* 房源ID
*/
@NotNull(message = "房源ID不能为空")
private Long houseId;
/**
* 评价内容
*/
@NotBlank(message = "评价内容不能为空")
@Size(min = 10, max = 500, message = "评价内容长度必须在10-500个字符之间")
private String content;
/**
* 总体评分:1-5星
*/
@NotNull(message = "总体评分不能为空")
@Min(value = 1, message = "评分最小为1星")
@Max(value = 5, message = "评分最大为5星")
private Integer rating;
/**
* 位置评分:1-5星
*/
@NotNull(message = "位置评分不能为空")
@Min(value = 1, message = "评分最小为1星")
@Max(value = 5, message = "评分最大为5星")
private Integer locationRating;
/**
* 清洁度评分:1-5星
*/
@NotNull(message = "清洁度评分不能为空")
@Min(value = 1, message = "评分最小为1星")
@Max(value = 5, message = "评分最大为5星")
private Integer cleanlinessRating;
/**
* 性价比评分:1-5星
*/
@NotNull(message = "性价比评分不能为空")
@Min(value = 1, message = "评分最小为1星")
@Max(value = 5, message = "评分最大为5星")
private Integer valueRating;
/**
* 房东评分:1-5星
*/
@NotNull(message = "房东评分不能为空")
@Min(value = 1, message = "评分最小为1星")
@Max(value = 5, message = "评分最大为5星")
private Integer landlordRating;
/**
* 评价图片
*/
private List<String> images;
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\dto\ReplyDTO.java
java
CopyInsert
package com.rental.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* 评价回复DTO
*/
@Data
public class ReplyDTO {
/**
* 评价ID
*/
@NotNull(message = "评价ID不能为空")
private Long reviewId;
/**
* 回复内容
*/
@NotBlank(message = "回复内容不能为空")
@Size(min = 5, max = 200, message = "回复内容长度必须在5-200个字符之间")
private String content;
}
2.3 DAO Interfaces
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\dao\AppointmentDao.java
java
CopyInsert
package com.rental.dao;
import com.rental.entity.Appointment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
* 预约DAO接口
*/
@Mapper
public interface AppointmentDao {
/**
* 根据ID查询预约
*/
Appointment selectById(Long id);
/**
* 根据用户ID查询预约列表
*/
List<Appointment> selectByUserId(Long userId);
/**
* 根据房东ID查询预约列表
*/
List<Appointment> selectByLandlordId(Long landlordId);
/**
* 根据房源ID查询预约列表
*/
List<Appointment> selectByHouseId(Long houseId);
/**
* 查询指定日期范围内的预约
*/
List<Appointment> selectByDateRange(@Param("startDate") Date startDate, @Param("endDate") Date endDate);
/**
* 查询预约列表
*/
List<Appointment> selectList(Appointment appointment);
/**
* 新增预约
*/
int insert(Appointment appointment);
/**
* 更新预约
*/
int update(Appointment appointment);
/**
* 更新预约状态
*/
int updateStatus(@Param("id") Long id, @Param("status") Integer status);
/**
* 删除预约
*/
int deleteById(Long id);
/**
* 检查时间冲突
*/
int checkTimeConflict(@Param("houseId") Long houseId, @Param("appointmentTime") Date appointmentTime);
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\dao\ViewingFeedbackDao.java
java
CopyInsert
package com.rental.dao;
import com.rental.entity.ViewingFeedback;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 看房反馈DAO接口
*/
@Mapper
public interface ViewingFeedbackDao {
/**
* 根据ID查询看房反馈
*/
ViewingFeedback selectById(Long id);
/**
* 根据预约ID查询看房反馈
*/
ViewingFeedback selectByAppointmentId(Long appointmentId);
/**
* 根据用户ID查询看房反馈列表
*/
List<ViewingFeedback> selectByUserId(Long userId);
/**
* 根据房源ID查询公开的看房反馈列表
*/
List<ViewingFeedback> selectPublicByHouseId(Long houseId);
/**
* 查询看房反馈列表
*/
List<ViewingFeedback> selectList(ViewingFeedback feedback);
/**
* 新增看房反馈
*/
int insert(ViewingFeedback feedback);
/**
* 更新看房反馈
*/
int update(ViewingFeedback feedback);
/**
* 更新是否公开
*/
int updateIsPublic(@Param("id") Long id, @Param("isPublic") Integer isPublic);
/**
* 删除看房反馈
*/
int deleteById(Long id);
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\dao\HouseReviewDao.java
java
CopyInsert
package com.rental.dao;
import com.rental.entity.HouseReview;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 房源评价DAO接口
*/
@Mapper
public interface HouseReviewDao {
/**
* 根据ID查询房源评价
*/
HouseReview selectById(Long id);
/**
* 根据房源ID查询房源评价列表
*/
List<HouseReview> selectByHouseId(Long houseId);
/**
* 根据用户ID查询房源评价列表
*/
List<HouseReview> selectByUserId(Long userId);
/**
* 根据房源ID和用户ID查询房源评价
*/
HouseReview selectByHouseIdAndUserId(@Param("houseId") Long houseId, @Param("userId") Long userId);
/**
* 查询房源评价列表
*/
List<HouseReview> selectList(HouseReview review);
/**
* 新增房源评价
*/
int insert(HouseReview review);
/**
* 更新房源评价
*/
int update(HouseReview review);
/**
* 删除房源评价
*/
int deleteById(Long id);
/**
* 计算房源平均评分
*/
Double calculateAverageRating(Long houseId);
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\dao\ReviewReplyDao.java
java
CopyInsert
package com.rental.dao;
import com.rental.entity.ReviewReply;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 评价回复DAO接口
*/
@Mapper
public interface ReviewReplyDao {
/**
* 根据ID查询评价回复
*/
ReviewReply selectById(Long id);
/**
* 根据评价ID查询评价回复列表
*/
List<ReviewReply> selectByReviewId(Long reviewId);
/**
* 根据用户ID查询评价回复列表
*/
List<ReviewReply> selectByUserId(Long userId);
/**
* 新增评价回复
*/
int insert(ReviewReply reply);
/**
* 更新评价回复
*/
int update(ReviewReply reply);
/**
* 删除评价回复
*/
int deleteById(Long id);
}
2.4 MyBatis Mapper XML Files
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\resources\mapper\AppointmentMapper.xml
xml
CopyInsert
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rental.dao.AppointmentDao">
<resultMap id="BaseResultMap" type="com.rental.entity.Appointment">
<id column="id" property="id"/>
<result column="house_id" property="houseId"/>
<result column="user_id" property="userId"/>
<result column="landlord_id" property="landlordId"/>
<result column="appointment_time" property="appointmentTime"/>
<result column="contact_name" property="contactName"/>
<result column="contact_phone" property="contactPhone"/>
<result column="appointment_notes" property="appointmentNotes"/>
<result column="status" property="status"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<sql id="Base_Column_List">
id, house_id, user_id, landlord_id, appointment_time, contact_name, contact_phone,
appointment_notes, status, create_time, update_time
</sql>
<select id="selectById" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
where id = #{id}
</select>
<select id="selectByUserId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
where user_id = #{userId}
order by appointment_time desc
</select>
<select id="selectByLandlordId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
where landlord_id = #{landlordId}
order by appointment_time desc
</select>
<select id="selectByHouseId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
where house_id = #{houseId}
order by appointment_time desc
</select>
<select id="selectByDateRange" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
where appointment_time between #{startDate} and #{endDate}
order by appointment_time
</select>
<select id="selectList" parameterType="com.rental.entity.Appointment" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from appointment
<where>
<if test="houseId != null">
and house_id = #{houseId}
</if>
<if test="userId != null">
and user_id = #{userId}
</if>
<if test="landlordId != null">
and landlord_id = #{landlordId}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
order by appointment_time desc
</select>
<select id="checkTimeConflict" resultType="java.lang.Integer">
select count(1)
from appointment
where house_id = #{houseId}
and status in (0, 1)
and appointment_time between date_sub(#{appointmentTime}, interval 1 hour)
and date_add(#{appointmentTime}, interval 1 hour)
</select>
<insert id="insert" parameterType="com.rental.entity.Appointment" useGeneratedKeys="true" keyProperty="id">
insert into appointment
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="houseId != null">house_id,</if>
<if test="userId != null">user_id,</if>
<if test="landlordId != null">landlord_id,</if>
<if test="appointmentTime != null">appointment_time,</if>
<if test="contactName != null">contact_name,</if>
<if test="contactPhone != null">contact_phone,</if>
<if test="appointmentNotes != null">appointment_notes,</if>
<if test="status != null">status,</if>
create_time,
update_time,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="houseId != null">#{houseId},</if>
<if test="userId != null">#{userId},</if>
<if test="landlordId != null">#{landlordId},</if>
<if test="appointmentTime != null">#{appointmentTime},</if>
<if test="contactName != null">#{contactName},</if>
<if test="contactPhone != null">#{contactPhone},</if>
<if test="appointmentNotes != null">#{appointmentNotes},</if>
<if test="status != null">#{status},</if>
now(),
now(),
</trim>
</insert>
<update id="update" parameterType="com.rental.entity.Appointment">
update appointment
<set>
<if test="appointmentTime != null">appointment_time = #{appointmentTime},</if>
<if test="contactName != null">contact_name = #{contactName},</if>
<if test="contactPhone != null">contact_phone = #{contactPhone},</if>
<if test="appointmentNotes != null">appointment_notes = #{appointmentNotes},</if>
<if test="status != null">status = #{status},</if>
update_time = now(),
</set>
where id = #{id}
</update>
<update id="updateStatus">
update appointment
set status = #{status}, update_time = now()
where id = #{id}
</update>
<delete id="deleteById" parameterType="java.lang.Long">
delete from appointment
where id = #{id}
</delete>
</mapper>
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\resources\mapper\ViewingFeedbackMapper.xml
xml
CopyInsert
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rental.dao.ViewingFeedbackDao">
<resultMap id="BaseResultMap" type="com.rental.entity.ViewingFeedback">
<id column="id" property="id"/>
<result column="appointment_id" property="appointmentId"/>
<result column="user_id" property="userId"/>
<result column="house_id" property="houseId"/>
<result column="feedback_content" property="feedbackContent"/>
<result column="satisfaction_level" property="satisfactionLevel"/>
<result column="is_public" property="isPublic"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<sql id="Base_Column_List">
id, appointment_id, user_id, house_id, feedback_content, satisfaction_level, is_public,
create_time, update_time
</sql>
<select id="selectById" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from viewing_feedback
where id = #{id}
</select>
<select id="selectByAppointmentId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from viewing_feedback
where appointment_id = #{appointmentId}
</select>
<select id="selectByUserId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from viewing_feedback
where user_id = #{userId}
order by create_time desc
</select>
<select id="selectPublicByHouseId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from viewing_feedback
where house_id = #{houseId} and is_public = 1
order by create_time desc
</select>
<select id="selectList" parameterType="com.rental.entity.ViewingFeedback" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from viewing_feedback
<where>
<if test="appointmentId != null">
and appointment_id = #{appointmentId}
</if>
<if test="userId != null">
and user_id = #{userId}
</if>
<if test="houseId != null">
and house_id = #{houseId}
</if>
<if test="isPublic != null">
and is_public = #{isPublic}
</if>
</where>
order by create_time desc
</select>
<insert id="insert" parameterType="com.rental.entity.ViewingFeedback" useGeneratedKeys="true" keyProperty="id">
insert into viewing_feedback
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="appointmentId != null">appointment_id,</if>
<if test="userId != null">user_id,</if>
<if test="houseId != null">house_id,</if>
<if test="feedbackContent != null">feedback_content,</if>
<if test="satisfactionLevel != null">satisfaction_level,</if>
<if test="isPublic != null">is_public,</if>
create_time,
update_time,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="appointmentId != null">#{appointmentId},</if>
<if test="userId != null">#{userId},</if>
<if test="houseId != null">#{houseId},</if>
<if test="feedbackContent != null">#{feedbackContent},</if>
<if test="satisfactionLevel != null">#{satisfactionLevel},</if>
<if test="isPublic != null">#{isPublic},</if>
now(),
now(),
</trim>
</insert>
<update id="update" parameterType="com.rental.entity.ViewingFeedback">
update viewing_feedback
<set>
<if test="feedbackContent != null">feedback_content = #{feedbackContent},</if>
<if test="satisfactionLevel != null">satisfaction_level = #{satisfactionLevel},</if>
<if test="isPublic != null">is_public = #{isPublic},</if>
update_time = now(),
</set>
where id = #{id}
</update>
<update id="updateIsPublic">
update viewing_feedback
set is_public = #{isPublic}, update_time = now()
where id = #{id}
</update>
<delete id="deleteById" parameterType="java.lang.Long">
delete from viewing_feedback
where id = #{id}
</delete>
</mapper>
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\resources\mapper\HouseReviewMapper.xml
xml
CopyInsert
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rental.dao.HouseReviewDao">
<resultMap id="BaseResultMap" type="com.rental.entity.HouseReview">
<id column="id" property="id"/>
<result column="house_id" property="houseId"/>
<result column="user_id" property="userId"/>
<result column="content" property="content"/>
<result column="rating" property="rating"/>
<result column="location_rating" property="locationRating"/>
<result column="cleanliness_rating" property="cleanlinessRating"/>
<result column="value_rating" property="valueRating"/>
<result column="landlord_rating" property="landlordRating"/>
<result column="images" property="images"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<sql id="Base_Column_List">
id, house_id, user_id, content, rating, location_rating, cleanliness_rating, value_rating,
landlord_rating, images, create_time, update_time
</sql>
<select id="selectById" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from house_review
where id = #{id}
</select>
<select id="selectByHouseId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from house_review
where house_id = #{houseId}
order by create_time desc
</select>
<select id="selectByUserId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from house_review
where user_id = #{userId}
order by create_time desc
</select>
<select id="selectByHouseIdAndUserId" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from house_review
where house_id = #{houseId} and user_id = #{userId}
limit 1
</select>
<select id="selectList" parameterType="com.rental.entity.HouseReview" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from house_review
<where>
<if test="houseId != null">
and house_id = #{houseId}
</if>
<if test="userId != null">
and user_id = #{userId}
</if>
<if test="rating != null">
and rating = #{rating}
</if>
</where>
order by create_time desc
</select>
<select id="calculateAverageRating" parameterType="java.lang.Long" resultType="java.lang.Double">
select avg(rating)
from house_review
where house_id = #{houseId}
</select>
<insert id="insert" parameterType="com.rental.entity.HouseReview" useGeneratedKeys="true" keyProperty="id">
insert into house_review
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="houseId != null">house_id,</if>
<if test="userId != null">user_id,</if>
<if test="content != null">content,</if>
<if test="rating != null">rating,</if>
<if test="locationRating != null">location_rating,</if>
<if test="cleanlinessRating != null">cleanliness_rating,</if>
<if test="valueRating != null">value_rating,</if>
<if test="landlordRating != null">landlord_rating,</if>
<if test="images != null">images,</if>
create_time,
update_time,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="houseId != null">#{houseId},</if>
<if test="userId != null">#{userId},</if>
<if test="content != null">#{content},</if>
<if test="rating != null">#{rating},</if>
<if test="locationRating != null">#{locationRating},</if>
<if test="cleanlinessRating != null">#{cleanlinessRating},</if>
<if test="valueRating != null">#{valueRating},</if>
<if test="landlordRating != null">#{landlordRating},</if>
<if test="images != null">#{images},</if>
now(),
now(),
</trim>
</insert>
<update id="update" parameterType="com.rental.entity.HouseReview">
update house_review
<set>
<if test="content != null">content = #{content},</if>
<if test="rating != null">rating = #{rating},</if>
<if test="locationRating != null">location_rating = #{locationRating},</if>
<if test="cleanlinessRating != null">cleanliness_rating = #{cleanlinessRating},</if>
<if test="valueRating != null">value_rating = #{valueRating},</if>
<if test="landlordRating != null">landlord_rating = #{landlordRating},</if>
<if test="images != null">images = #{images},</if>
update_time = now(),
</set>
where id = #{id}
</update>
<delete id="deleteById" parameterType="java.lang.Long">
delete from house_review
where id = #{id}
</delete>
</mapper>
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\resources\mapper\ReviewReplyMapper.xml
xml
CopyInsert
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rental.dao.ReviewReplyDao">
<resultMap id="BaseResultMap" type="com.rental.entity.ReviewReply">
<id column="id" property="id"/>
<result column="review_id" property="reviewId"/>
<result column="user_id" property="userId"/>
<result column="content" property="content"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<sql id="Base_Column_List">
id, review_id, user_id, content, create_time, update_time
</sql>
<select id="selectById" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from review_reply
where id = #{id}
</select>
<select id="selectByReviewId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from review_reply
where review_id = #{reviewId}
order by create_time asc
</select>
<select id="selectByUserId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from review_reply
where user_id = #{userId}
order by create_time desc
</select>
<insert id="insert" parameterType="com.rental.entity.ReviewReply" useGeneratedKeys="true" keyProperty="id">
insert into review_reply
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="reviewId != null">review_id,</if>
<if test="userId != null">user_id,</if>
<if test="content != null">content,</if>
create_time,
update_time,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="reviewId != null">#{reviewId},</if>
<if test="userId != null">#{userId},</if>
<if test="content != null">#{content},</if>
now(),
now(),
</trim>
</insert>
<update id="update" parameterType="com.rental.entity.ReviewReply">
update review_reply
<set>
<if test="content != null">content = #{content},</if>
update_time = now(),
</set>
where id = #{id}
</update>
<delete id="deleteById" parameterType="java.lang.Long">
delete from review_reply
where id = #{id}
</delete>
</mapper>
2.5 Service Interfaces
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\service\AppointmentService.java
java
CopyInsert
package com.rental.service;
import com.rental.dto.AppointmentDTO;
import com.rental.entity.Appointment;
import com.rental.dto.Result;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 预约服务接口
*/
public interface AppointmentService {
/**
* 创建预约
*/
Result<Appointment> createAppointment(AppointmentDTO appointmentDTO, Long userId);
/**
* 确认预约
*/
Result<Void> confirmAppointment(Long appointmentId, Long landlordId);
/**
* 取消预约
*/
Result<Void> cancelAppointment(Long appointmentId, Long userId);
/**
* 完成预约
*/
Result<Void> completeAppointment(Long appointmentId, Long landlordId);
/**
* 获取预约详情
*/
Result<Appointment> getAppointmentById(Long appointmentId);
/**
* 获取用户的预约列表
*/
Result<List<Appointment>> getUserAppointments(Long userId);
/**
* 获取房东的预约列表
*/
Result<List<Appointment>> getLandlordAppointments(Long landlordId);
/**
* 获取房源的预约列表
*/
Result<List<Appointment>> getHouseAppointments(Long houseId);
/**
* 获取指定日期范围内的预约
*/
Result<List<Appointment>> getAppointmentsByDateRange(Date startDate, Date endDate, Long userId);
/**
* 获取预约统计数据
*/
Result<Map<String, Object>> getAppointmentStatistics(Long userId);
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\service\ViewingFeedbackService.java
java
CopyInsert
package com.rental.service;
import com.rental.dto.FeedbackDTO;
import com.rental.entity.ViewingFeedback;
import com.rental.dto.Result;
import java.util.List;
/**
* 看房反馈服务接口
*/
public interface ViewingFeedbackService {
/**
* 提交看房反馈
*/
Result<ViewingFeedback> submitFeedback(FeedbackDTO feedbackDTO, Long userId);
/**
* 更新看房反馈
*/
Result<Void> updateFeedback(Long feedbackId, FeedbackDTO feedbackDTO, Long userId);
/**
* 删除看房反馈
*/
Result<Void> deleteFeedback(Long feedbackId, Long userId);
/**
* 设置反馈是否公开
*/
Result<Void> setFeedbackPublic(Long feedbackId, Integer isPublic, Long userId);
/**
* 获取反馈详情
*/
Result<ViewingFeedback> getFeedbackById(Long feedbackId);
/**
* 获取用户的反馈列表
*/
Result<List<ViewingFeedback>> getUserFeedbacks(Long userId);
/**
* 获取房源的公开反馈列表
*/
Result<List<ViewingFeedback>> getHousePublicFeedbacks(Long houseId);
/**
* 获取预约的反馈
*/
Result<ViewingFeedback> getAppointmentFeedback(Long appointmentId);
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\service\HouseReviewService.java
java
CopyInsert
package com.rental.service;
import com.rental.dto.ReviewDTO;
import com.rental.entity.HouseReview;
import com.rental.dto.Result;
import java.util.List;
import java.util.Map;
/**
* 房源评价服务接口
*/
public interface HouseReviewService {
/**
* 提交房源评价
*/
Result<HouseReview> submitReview(ReviewDTO reviewDTO, Long userId);
/**
* 更新房源评价
*/
Result<Void> updateReview(Long reviewId, ReviewDTO reviewDTO, Long userId);
/**
* 删除房源评价
*/
Result<Void> deleteReview(Long reviewId, Long userId);
/**
* 获取评价详情
*/
Result<HouseReview> getReviewById(Long reviewId);
/**
* 获取房源的评价列表
*/
Result<List<HouseReview>> getHouseReviews(Long houseId);
/**
* 获取用户的评价列表
*/
Result<List<HouseReview>> getUserReviews(Long userId);
/**
* 获取房源评价统计数据
*/
Result<Map<String, Object>> getHouseReviewStatistics(Long houseId);
/**
* 检查用户是否已评价房源
*/
Result<Boolean> checkUserReviewed(Long houseId, Long userId);
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\service\ReviewReplyService.java
java
CopyInsert
package com.rental.service;
import com.rental.dto.ReplyDTO;
import com.rental.entity.ReviewReply;
import com.rental.dto.Result;
import java.util.List;
/**
* 评价回复服务接口
*/
public interface ReviewReplyService {
/**
* 提交评价回复
*/
Result<ReviewReply> submitReply(ReplyDTO replyDTO, Long userId);
/**
* 更新评价回复
*/
Result<Void> updateReply(Long replyId, ReplyDTO replyDTO, Long userId);
/**
* 删除评价回复
*/
Result<Void> deleteReply(Long replyId, Long userId);
/**
* 获取回复详情
*/
Result<ReviewReply> getReplyById(Long replyId);
/**
* 获取评价的回复列表
*/
Result<List<ReviewReply>> getReviewReplies(Long reviewId);
}
2.6 Service Implementations
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\service\impl\AppointmentServiceImpl.java
java
CopyInsert
package com.rental.service.impl;
import com.rental.dao.AppointmentDao;
import com.rental.dao.HouseDao;
import com.rental.dto.AppointmentDTO;
import com.rental.entity.Appointment;
import com.rental.entity.House;
import com.rental.dto.Result;
import com.rental.exception.BusinessException;
import com.rental.service.AppointmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
/**
* 预约服务实现类
*/
@Service
public class AppointmentServiceImpl implements AppointmentService {
@Autowired
private AppointmentDao appointmentDao;
@Autowired
private HouseDao houseDao;
@Override
@Transactional
public Result<Appointment> createAppointment(AppointmentDTO appointmentDTO, Long userId) {
// 检查房源是否存在
House house = houseDao.selectById(appointmentDTO.getHouseId());
if (house == null) {
return Result.error(404, "房源不存在");
}
// 检查房源状态是否正常
if (house.getStatus() != 1) {
return Result.error(400, "房源状态异常,无法预约");
}
// 检查时间冲突
int conflict = appointmentDao.checkTimeConflict(appointmentDTO.getHouseId(), appointmentDTO.getAppointmentTime());
if (conflict > 0) {
return Result.error(400, "该时间段已有预约,请选择其他时间");
}
// 创建预约
Appointment appointment = new Appointment();
appointment.setHouseId(appointmentDTO.getHouseId());
appointment.setUserId(userId);
appointment.setLandlordId(house.getLandlordId());
appointment.setAppointmentTime(appointmentDTO.getAppointmentTime());
appointment.setContactName(appointmentDTO.getContactName());
appointment.setContactPhone(appointmentDTO.getContactPhone());
appointment.setAppointmentNotes(appointmentDTO.getAppointmentNotes());
appointment.setStatus(0); // 待确认
appointmentDao.insert(appointment);
return Result.success(appointment);
}
@Override
@Transactional
public Result<Void> confirmAppointment(Long appointmentId, Long landlordId) {
Appointment appointment = appointmentDao.selectById(appointmentId);
if (appointment == null) {
return Result.error(404, "预约不存在");
}
// 检查是否是房东本人操作
if (!appointment.getLandlordId().equals(landlordId)) {
return Result.error(403, "无权操作此预约");
}
// 检查预约状态
if (appointment.getStatus() != 0) {
return Result.error(400, "预约状态异常,无法确认");
}
// 更新预约状态为已确认
appointmentDao.updateStatus(appointmentId, 1);
return Result.success();
}
@Override
@Transactional
public Result<Void> cancelAppointment(Long appointmentId, Long userId) {
Appointment appointment = appointmentDao.selectById(appointmentId);
if (appointment == null) {
return Result.error(404, "预约不存在");
}
// 检查是否是用户本人或房东操作
if (!appointment.getUserId().equals(userId) && !appointment.getLandlordId().equals(userId)) {
return Result.error(403, "无权操作此预约");
}
// 检查预约状态
if (appointment.getStatus() > 1) {
return Result.error(400, "预约状态异常,无法取消");
}
// 更新预约状态为已取消
appointmentDao.updateStatus(appointmentId, 2);
return Result.success();
}
@Override
@Transactional
public Result<Void> completeAppointment(Long appointmentId, Long landlordId) {
Appointment appointment = appointmentDao.selectById(appointmentId);
if (appointment == null) {
return Result.error(404, "预约不存在");
}
// 检查是否是房东本人操作
if (!appointment.getLandlordId().equals(landlordId)) {
return Result.error(403, "无权操作此预约");
}
// 检查预约状态
if (appointment.getStatus() != 1) {
return Result.error(400, "预约状态异常,无法完成");
}
// 更新预约状态为已完成
appointmentDao.updateStatus(appointmentId, 3);
return Result.success();
}
@Override
public Result<Appointment> getAppointmentById(Long appointmentId) {
Appointment appointment = appointmentDao.selectById(appointmentId);
if (appointment == null) {
return Result.error(404, "预约不存在");
}
return Result.success(appointment);
}
@Override
public Result<List<Appointment>> getUserAppointments(Long userId) {
List<Appointment> appointments = appointmentDao.selectByUserId(userId);
return Result.success(appointments);
}
@Override
public Result<List<Appointment>> getLandlordAppointments(Long landlordId) {
List<Appointment> appointments = appointmentDao.selectByLandlordId(landlordId);
return Result.success(appointments);
}
@Override
public Result<List<Appointment>> getHouseAppointments(Long houseId) {
List<Appointment> appointments = appointmentDao.selectByHouseId(houseId);
return Result.success(appointments);
}
@Override
public Result<List<Appointment>> getAppointmentsByDateRange(Date startDate, Date endDate, Long userId) {
if (startDate == null || endDate == null) {
return Result.error(400, "日期范围不能为空");
}
if (startDate.after(endDate)) {
return Result.error(400, "开始日期不能晚于结束日期");
}
List<Appointment> appointments = appointmentDao.selectByDateRange(startDate, endDate);
// 过滤出与用户相关的预约
List<Appointment> filteredAppointments = new ArrayList<>();
for (Appointment appointment : appointments) {
if (appointment.getUserId().equals(userId) || appointment.getLandlordId().equals(userId)) {
filteredAppointments.add(appointment);
}
}
return Result.success(filteredAppointments);
}
@Override
public Result<Map<String, Object>> getAppointmentStatistics(Long userId) {
Map<String, Object> statistics = new HashMap<>();
Appointment query = new Appointment();
query.setUserId(userId);
List<Appointment> userAppointments = appointmentDao.selectList(query);
// 统计各状态预约数量
int pendingCount = 0;
int confirmedCount = 0;
int canceledCount = 0;
int completedCount = 0;
for (Appointment appointment : userAppointments) {
switch (appointment.getStatus()) {
case 0:
pendingCount++;
break;
case 1:
confirmedCount++;
break;
case 2:
canceledCount++;
break;
case 3:
completedCount++;
break;
}
}
statistics.put("total", userAppointments.size());
statistics.put("pending", pendingCount);
statistics.put("confirmed", confirmedCount);
statistics.put("canceled", canceledCount);
statistics.put("completed", completedCount);
return Result.success(statistics);
}
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\service\impl\ViewingFeedbackServiceImpl.java
java
CopyInsert
package com.rental.service.impl;
import com.rental.dao.AppointmentDao;
import com.rental.dao.HouseDao;
import com.rental.dao.ViewingFeedbackDao;
import com.rental.dto.FeedbackDTO;
import com.rental.entity.Appointment;
import com.rental.entity.House;
import com.rental.entity.ViewingFeedback;
import com.rental.dto.Result;
import com.rental.service.ViewingFeedbackService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 看房反馈服务实现类
*/
@Service
public class ViewingFeedbackServiceImpl implements ViewingFeedbackService {
@Autowired
private ViewingFeedbackDao viewingFeedbackDao;
@Autowired
private AppointmentDao appointmentDao;
@Autowired
private HouseDao houseDao;
@Override
@Transactional
public Result<ViewingFeedback> submitFeedback(FeedbackDTO feedbackDTO, Long userId) {
// 检查预约是否存在
Appointment appointment = appointmentDao.selectById(feedbackDTO.getAppointmentId());
if (appointment == null) {
return Result.error(404, "预约不存在");
}
// 检查是否是预约用户本人
if (!appointment.getUserId().equals(userId)) {
return Result.error(403, "无权提交此预约的反馈");
}
// 检查预约状态是否已完成
if (appointment.getStatus() != 3) {
return Result.error(400, "预约尚未完成,无法提交反馈");
}
// 检查是否已提交过反馈
ViewingFeedback existingFeedback = viewingFeedbackDao.selectByAppointmentId(feedbackDTO.getAppointmentId());
if (existingFeedback != null) {
return Result.error(400, "已提交过反馈,不能重复提交");
}
// 创建反馈
ViewingFeedback feedback = new ViewingFeedback();
feedback.setAppointmentId(feedbackDTO.getAppointmentId());
feedback.setUserId(userId);
feedback.setHouseId(appointment.getHouseId());
feedback.setFeedbackContent(feedbackDTO.getFeedbackContent());
feedback.setSatisfactionLevel(feedbackDTO.getSatisfactionLevel());
feedback.setIsPublic(feedbackDTO.getIsPublic());
viewingFeedbackDao.insert(feedback);
return Result.success(feedback);
}
@Override
@Transactional
public Result<Void> updateFeedback(Long feedbackId, FeedbackDTO feedbackDTO, Long userId) {
// 检查反馈是否存在
ViewingFeedback feedback = viewingFeedbackDao.selectById(feedbackId);
if (feedback == null) {
return Result.error(404, "反馈不存在");
}
// 检查是否是反馈用户本人
if (!feedback.getUserId().equals(userId)) {
return Result.error(403, "无权修改此反馈");
}
// 更新反馈
feedback.setFeedbackContent(feedbackDTO.getFeedbackContent());
feedback.setSatisfactionLevel(feedbackDTO.getSatisfactionLevel());
feedback.setIsPublic(feedbackDTO.getIsPublic());
viewingFeedbackDao.update(feedback);
return Result.success();
}
@Override
@Transactional
public Result<Void> deleteFeedback(Long feedbackId, Long userId) {
// 检查反馈是否存在
ViewingFeedback feedback = viewingFeedbackDao.selectById(feedbackId);
if (feedback == null) {
return Result.error(404, "反馈不存在");
}
// 检查是否是反馈用户本人
if (!feedback.getUserId().equals(userId)) {
return Result.error(403, "无权删除此反馈");
}
viewingFeedbackDao.deleteById(feedbackId);
return Result.success();
}
@Override
@Transactional
public Result<Void> setFeedbackPublic(Long feedbackId, Integer isPublic, Long userId) {
// 检查反馈是否存在
ViewingFeedback feedback = viewingFeedbackDao.selectById(feedbackId);
if (feedback == null) {
return Result.error(404, "反馈不存在");
}
// 检查是否是反馈用户本人
if (!feedback.getUserId().equals(userId)) {
return Result.error(403, "无权修改此反馈");
}
// 更新公开状态
viewingFeedbackDao.updateIsPublic(feedbackId, isPublic);
return Result.success();
}
@Override
public Result<ViewingFeedback> getFeedbackById(Long feedbackId) {
ViewingFeedback feedback = viewingFeedbackDao.selectById(feedbackId);
if (feedback == null) {
return Result.error(404, "反馈不存在");
}
return Result.success(feedback);
}
@Override
public Result<List<ViewingFeedback>> getUserFeedbacks(Long userId) {
List<ViewingFeedback> feedbacks = viewingFeedbackDao.selectByUserId(userId);
return Result.success(feedbacks);
}
@Override
public Result<List<ViewingFeedback>> getHousePublicFeedbacks(Long houseId) {
List<ViewingFeedback> feedbacks = viewingFeedbackDao.selectPublicByHouseId(houseId);
return Result.success(feedbacks);
}
@Override
public Result<ViewingFeedback> getAppointmentFeedback(Long appointmentId) {
ViewingFeedback feedback = viewingFeedbackDao.selectByAppointmentId(appointmentId);
if (feedback == null) {
return Result.error(404, "该预约暂无反馈");
}
return Result.success(feedback);
}
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\service\impl\HouseReviewServiceImpl.java
java
CopyInsert
package com.rental.service.impl;
import com.rental.dao.HouseDao;
import com.rental.dao.HouseReviewDao;
import com.rental.dto.ReviewDTO;
import com.rental.entity.House;
import com.rental.entity.HouseReview;
import com.rental.dto.Result;
import com.rental.service.HouseReviewService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 房源评价服务实现类
*/
@Service
public class HouseReviewServiceImpl implements HouseReviewService {
@Autowired
private HouseReviewDao houseReviewDao;
@Autowired
private HouseDao houseDao;
@Override
@Transactional
public Result<HouseReview> submitReview(ReviewDTO reviewDTO, Long userId) {
// 检查房源是否存在
House house = houseDao.selectById(reviewDTO.getHouseId());
if (house == null) {
return Result.error(404, "房源不存在");
}
// 检查用户是否已评价过该房源
HouseReview existingReview = houseReviewDao.selectByHouseIdAndUserId(reviewDTO.getHouseId(), userId);
if (existingReview != null) {
return Result.error(400, "您已评价过该房源,不能重复评价");
}
// 创建评价
HouseReview review = new HouseReview();
review.setHouseId(reviewDTO.getHouseId());
review.setUserId(userId);
review.setContent(reviewDTO.getContent());
review.setRating(reviewDTO.getRating());
review.setLocationRating(reviewDTO.getLocationRating());
review.setCleanlinessRating(reviewDTO.getCleanlinessRating());
review.setValueRating(reviewDTO.getValueRating());
review.setLandlordRating(reviewDTO.getLandlordRating());
// 处理评价图片
if (reviewDTO.getImages() != null && !reviewDTO.getImages().isEmpty()) {
String images = String.join(",", reviewDTO.getImages());
review.setImages(images);
}
houseReviewDao.insert(review);
// 更新房源评分
updateHouseRating(reviewDTO.getHouseId());
return Result.success(review);
}
@Override
@Transactional
public Result<Void> updateReview(Long reviewId, ReviewDTO reviewDTO, Long userId) {
// 检查评价是否存在
HouseReview review = houseReviewDao.selectById(reviewId);
if (review == null) {
return Result.error(404, "评价不存在");
}
// 检查是否是评价用户本人
if (!review.getUserId().equals(userId)) {
return Result.error(403, "无权修改此评价");
}
// 更新评价
review.setContent(reviewDTO.getContent());
review.setRating(reviewDTO.getRating());
review.setLocationRating(reviewDTO.getLocationRating());
review.setCleanlinessRating(reviewDTO.getCleanlinessRating());
review.setValueRating(reviewDTO.getValueRating());
review.setLandlordRating(reviewDTO.getLandlordRating());
// 处理评价图片
if (reviewDTO.getImages() != null && !reviewDTO.getImages().isEmpty()) {
String images = String.join(",", reviewDTO.getImages());
review.setImages(images);
}
houseReviewDao.update(review);
// 更新房源评分
updateHouseRating(review.getHouseId());
return Result.success();
}
@Override
@Transactional
public Result<Void> deleteReview(Long reviewId, Long userId) {
// 检查评价是否存在
HouseReview review = houseReviewDao.selectById(reviewId);
if (review == null) {
return Result.error(404, "评价不存在");
}
// 检查是否是评价用户本人
if (!review.getUserId().equals(userId)) {
return Result.error(403, "无权删除此评价");
}
houseReviewDao.deleteById(reviewId);
// 更新房源评分
updateHouseRating(review.getHouseId());
return Result.success();
}
@Override
public Result<HouseReview> getReviewById(Long reviewId) {
HouseReview review = houseReviewDao.selectById(reviewId);
if (review == null) {
return Result.error(404, "评价不存在");
}
return Result.success(review);
}
@Override
public Result<List<HouseReview>> getHouseReviews(Long houseId) {
List<HouseReview> reviews = houseReviewDao.selectByHouseId(houseId);
return Result.success(reviews);
}
@Override
public Result<List<HouseReview>> getUserReviews(Long userId) {
List<HouseReview> reviews = houseReviewDao.selectByUserId(userId);
return Result.success(reviews);
}
@Override
public Result<Map<String, Object>> getHouseReviewStatistics(Long houseId) {
List<HouseReview> reviews = houseReviewDao.selectByHouseId(houseId);
Map<String, Object> statistics = new HashMap<>();
if (reviews.isEmpty()) {
statistics.put("totalCount", 0);
statistics.put("averageRating", 0.0);
statistics.put("averageLocationRating", 0.0);
statistics.put("averageCleanlinessRating", 0.0);
statistics.put("averageValueRating", 0.0);
statistics.put("averageLandlordRating", 0.0);
statistics.put("ratingDistribution", new int[]{0, 0, 0, 0, 0});
return Result.success(statistics);
}
// 计算平均评分
double averageRating = reviews.stream().mapToInt(HouseReview::getRating).average().orElse(0.0);
double averageLocationRating = reviews.stream().mapToInt(HouseReview::getLocationRating).average().orElse(0.0);
double averageCleanlinessRating = reviews.stream().mapToInt(HouseReview::getCleanlinessRating).average().orElse(0.0);
double averageValueRating = reviews.stream().mapToInt(HouseReview::getValueRating).average().orElse(0.0);
double averageLandlordRating = reviews.stream().mapToInt(HouseReview::getLandlordRating).average().orElse(0.0);
// 计算评分分布
int[] ratingDistribution = new int[5]; // 1-5星评分分布
for (HouseReview review : reviews) {
ratingDistribution[review.getRating() - 1]++;
}
statistics.put("totalCount", reviews.size());
statistics.put("averageRating", averageRating);
statistics.put("averageLocationRating", averageLocationRating);
statistics.put("averageCleanlinessRating", averageCleanlinessRating);
statistics.put("averageValueRating", averageValueRating);
statistics.put("averageLandlordRating", averageLandlordRating);
statistics.put("ratingDistribution", ratingDistribution);
return Result.success(statistics);
}
@Override
public Result<Boolean> checkUserReviewed(Long houseId, Long userId) {
HouseReview review = houseReviewDao.selectByHouseIdAndUserId(houseId, userId);
return Result.success(review != null);
}
/**
* 更新房源评分
*/
private void updateHouseRating(Long houseId) {
Double averageRating = houseReviewDao.calculateAverageRating(houseId);
if (averageRating != null) {
House house = houseDao.selectById(houseId);
if (house != null) {
// 这里假设House实体类中有rating字段,实际项目中需要根据具体情况调整
// house.setRating(averageRating);
// houseDao.update(house);
}
}
}
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\service\impl\ReviewReplyServiceImpl.java
java
CopyInsert
package com.rental.service.impl;
import com.rental.dao.HouseReviewDao;
import com.rental.dao.ReviewReplyDao;
import com.rental.dto.ReplyDTO;
import com.rental.entity.HouseReview;
import com.rental.entity.ReviewReply;
import com.rental.dto.Result;
import com.rental.service.ReviewReplyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 评价回复服务实现类
*/
@Service
public class ReviewReplyServiceImpl implements ReviewReplyService {
@Autowired
private ReviewReplyDao reviewReplyDao;
@Autowired
private HouseReviewDao houseReviewDao;
@Override
@Transactional
public Result<ReviewReply> submitReply(ReplyDTO replyDTO, Long userId) {
// 检查评价是否存在
HouseReview review = houseReviewDao.selectById(replyDTO.getReviewId());
if (review == null) {
return Result.error(404, "评价不存在");
}
// 创建回复
ReviewReply reply = new ReviewReply();
reply.setReviewId(replyDTO.getReviewId());
reply.setUserId(userId);
reply.setContent(replyDTO.getContent());
reviewReplyDao.insert(reply);
return Result.success(reply);
}
@Override
@Transactional
public Result<Void> updateReply(Long replyId, ReplyDTO replyDTO, Long userId) {
// 检查回复是否存在
ReviewReply reply = reviewReplyDao.selectById(replyId);
if (reply == null) {
return Result.error(404, "回复不存在");
}
// 检查是否是回复用户本人
if (!reply.getUserId().equals(userId)) {
return Result.error(403, "无权修改此回复");
}
// 更新回复
reply.setContent(replyDTO.getContent());
reviewReplyDao.update(reply);
return Result.success();
}
@Override
@Transactional
public Result<Void> deleteReply(Long replyId, Long userId) {
// 检查回复是否存在
ReviewReply reply = reviewReplyDao.selectById(replyId);
if (reply == null) {
return Result.error(404, "回复不存在");
}
// 检查是否是回复用户本人
if (!reply.getUserId().equals(userId)) {
return Result.error(403, "无权删除此回复");
}
reviewReplyDao.deleteById(replyId);
return Result.success();
}
@Override
public Result<ReviewReply> getReplyById(Long replyId) {
ReviewReply reply = reviewReplyDao.selectById(replyId);
if (reply == null) {
return Result.error(404, "回复不存在");
}
return Result.success(reply);
}
@Override
public Result<List<ReviewReply>> getReviewReplies(Long reviewId) {
List<ReviewReply> replies = reviewReplyDao.selectByReviewId(reviewId);
return Result.success(replies);
}
}
2.7 Controllers
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\controller\AppointmentController.java
java
CopyInsert
package com.rental.controller;
import com.rental.dto.AppointmentDTO;
import com.rental.entity.Appointment;
import com.rental.dto.Result;
import com.rental.service.AppointmentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 预约控制器
*/
@RestController
@RequestMapping("/appointment")
public class AppointmentController {
@Autowired
private AppointmentService appointmentService;
/**
* 创建预约
*/
@PostMapping("/create")
public Result<Appointment> createAppointment(@RequestBody @Valid AppointmentDTO appointmentDTO, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return appointmentService.createAppointment(appointmentDTO, userId);
}
/**
* 确认预约
*/
@PostMapping("/confirm/{id}")
public Result<Void> confirmAppointment(@PathVariable("id") Long appointmentId, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return appointmentService.confirmAppointment(appointmentId, userId);
}
/**
* 取消预约
*/
@PostMapping("/cancel/{id}")
public Result<Void> cancelAppointment(@PathVariable("id") Long appointmentId, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return appointmentService.cancelAppointment(appointmentId, userId);
}
/**
* 完成预约
*/
@PostMapping("/complete/{id}")
public Result<Void> completeAppointment(@PathVariable("id") Long appointmentId, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return appointmentService.completeAppointment(appointmentId, userId);
}
/**
* 获取预约详情
*/
@GetMapping("/detail/{id}")
public Result<Appointment> getAppointmentDetail(@PathVariable("id") Long appointmentId) {
return appointmentService.getAppointmentById(appointmentId);
}
/**
* 获取用户的预约列表
*/
@GetMapping("/user")
public Result<List<Appointment>> getUserAppointments(HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return appointmentService.getUserAppointments(userId);
}
/**
* 获取房东的预约列表
*/
@GetMapping("/landlord")
public Result<List<Appointment>> getLandlordAppointments(HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return appointmentService.getLandlordAppointments(userId);
}
/**
* 获取房源的预约列表
*/
@GetMapping("/house/{houseId}")
public Result<List<Appointment>> getHouseAppointments(@PathVariable("houseId") Long houseId) {
return appointmentService.getHouseAppointments(houseId);
}
/**
* 获取指定日期范围内的预约
*/
@GetMapping("/date-range")
public Result<List<Appointment>> getAppointmentsByDateRange(
@RequestParam("startDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@RequestParam("endDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return appointmentService.getAppointmentsByDateRange(startDate, endDate, userId);
}
/**
* 获取预约统计数据
*/
@GetMapping("/statistics")
public Result<Map<String, Object>> getAppointmentStatistics(HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return appointmentService.getAppointmentStatistics(userId);
}
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\controller\ViewingFeedbackController.java
java
CopyInsert
package com.rental.controller;
import com.rental.dto.FeedbackDTO;
import com.rental.entity.ViewingFeedback;
import com.rental.dto.Result;
import com.rental.service.ViewingFeedbackService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
/**
* 看房反馈控制器
*/
@RestController
@RequestMapping("/feedback")
public class ViewingFeedbackController {
@Autowired
private ViewingFeedbackService viewingFeedbackService;
/**
* 提交看房反馈
*/
@PostMapping("/submit")
public Result<ViewingFeedback> submitFeedback(@RequestBody @Valid FeedbackDTO feedbackDTO, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return viewingFeedbackService.submitFeedback(feedbackDTO, userId);
}
/**
* 更新看房反馈
*/
@PutMapping("/update/{id}")
public Result<Void> updateFeedback(@PathVariable("id") Long feedbackId, @RequestBody @Valid FeedbackDTO feedbackDTO, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return viewingFeedbackService.updateFeedback(feedbackId, feedbackDTO, userId);
}
/**
* 删除看房反馈
*/
@DeleteMapping("/delete/{id}")
public Result<Void> deleteFeedback(@PathVariable("id") Long feedbackId, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return viewingFeedbackService.deleteFeedback(feedbackId, userId);
}
/**
* 设置反馈是否公开
*/
@PutMapping("/public/{id}")
public Result<Void> setFeedbackPublic(@PathVariable("id") Long feedbackId, @RequestParam("isPublic") Integer isPublic, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return viewingFeedbackService.setFeedbackPublic(feedbackId, isPublic, userId);
}
/**
* 获取反馈详情
*/
@GetMapping("/detail/{id}")
public Result<ViewingFeedback> getFeedbackDetail(@PathVariable("id") Long feedbackId) {
return viewingFeedbackService.getFeedbackById(feedbackId);
}
/**
* 获取用户的反馈列表
*/
@GetMapping("/user")
public Result<List<ViewingFeedback>> getUserFeedbacks(HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return viewingFeedbackService.getUserFeedbacks(userId);
}
/**
* 获取房源的公开反馈列表
*/
@GetMapping("/house/{houseId}")
public Result<List<ViewingFeedback>> getHousePublicFeedbacks(@PathVariable("houseId") Long houseId) {
return viewingFeedbackService.getHousePublicFeedbacks(houseId);
}
/**
* 获取预约的反馈
*/
@GetMapping("/appointment/{appointmentId}")
public Result<ViewingFeedback> getAppointmentFeedback(@PathVariable("appointmentId") Long appointmentId) {
return viewingFeedbackService.getAppointmentFeedback(appointmentId);
}
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\controller\HouseReviewController.java
java
CopyInsert
package com.rental.controller;
import com.rental.dto.ReviewDTO;
import com.rental.entity.HouseReview;
import com.rental.dto.Result;
import com.rental.service.HouseReviewService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
/**
* 房源评价控制器
*/
@RestController
@RequestMapping("/review")
public class HouseReviewController {
@Autowired
private HouseReviewService houseReviewService;
/**
* 提交房源评价
*/
@PostMapping("/submit")
public Result<HouseReview> submitReview(@RequestBody @Valid ReviewDTO reviewDTO, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return houseReviewService.submitReview(reviewDTO, userId);
}
/**
* 更新房源评价
*/
@PutMapping("/update/{id}")
public Result<Void> updateReview(@PathVariable("id") Long reviewId, @RequestBody @Valid ReviewDTO reviewDTO, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return houseReviewService.updateReview(reviewId, reviewDTO, userId);
}
/**
* 删除房源评价
*/
@DeleteMapping("/delete/{id}")
public Result<Void> deleteReview(@PathVariable("id") Long reviewId, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return houseReviewService.deleteReview(reviewId, userId);
}
/**
* 获取评价详情
*/
@GetMapping("/detail/{id}")
public Result<HouseReview> getReviewDetail(@PathVariable("id") Long reviewId) {
return houseReviewService.getReviewById(reviewId);
}
/**
* 获取房源的评价列表
*/
@GetMapping("/house/{houseId}")
public Result<List<HouseReview>> getHouseReviews(@PathVariable("houseId") Long houseId) {
return houseReviewService.getHouseReviews(houseId);
}
/**
* 获取用户的评价列表
*/
@GetMapping("/user")
public Result<List<HouseReview>> getUserReviews(HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return houseReviewService.getUserReviews(userId);
}
/**
* 获取房源评价统计数据
*/
@GetMapping("/statistics/{houseId}")
public Result<Map<String, Object>> getHouseReviewStatistics(@PathVariable("houseId") Long houseId) {
return houseReviewService.getHouseReviewStatistics(houseId);
}
/**
* 检查用户是否已评价房源
*/
@GetMapping("/check/{houseId}")
public Result<Boolean> checkUserReviewed(@PathVariable("houseId") Long houseId, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return houseReviewService.checkUserReviewed(houseId, userId);
}
}
File: d:\htsun\Docs\资料\文章\rental-system\backend\src\main\java\com\rental\controller\ReviewReplyController.java
java
CopyInsert
package com.rental.controller;
import com.rental.dto.ReplyDTO;
import com.rental.entity.ReviewReply;
import com.rental.dto.Result;
import com.rental.service.ReviewReplyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
/**
* 评价回复控制器
*/
@RestController
@RequestMapping("/reply")
public class ReviewReplyController {
@Autowired
private ReviewReplyService reviewReplyService;
/**
* 提交评价回复
*/
@PostMapping("/submit")
public Result<ReviewReply> submitReply(@RequestBody @Valid ReplyDTO replyDTO, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return reviewReplyService.submitReply(replyDTO, userId);
}
/**
* 更新评价回复
*/
@PutMapping("/update/{id}")
public Result<Void> updateReply(@PathVariable("id") Long replyId, @RequestBody @Valid ReplyDTO replyDTO, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return reviewReplyService.updateReply(replyId, replyDTO, userId);
}
/**
* 删除评价回复
*/
@DeleteMapping("/delete/{id}")
public Result<Void> deleteReply(@PathVariable("id") Long replyId, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
return reviewReplyService.deleteReply(replyId, userId);
}
/**
* 获取回复详情
*/
@GetMapping("/detail/{id}")
public Result<ReviewReply> getReplyDetail(@PathVariable("id") Long replyId) {
return reviewReplyService.getReplyById(replyId);
}
/**
* 获取评价的回复列表
*/
@GetMapping("/review/{reviewId}")
public Result<List<ReviewReply>> getReviewReplies(@PathVariable("reviewId") Long reviewId) {
return reviewReplyService.getReviewReplies(reviewId);
}
}
3.1 API Services
File: d:\htsun\Docs\资料\文章\rental-system\frontend\src\api\appointment.js
javascript
CopyInsert
import request from '@/utils/request';
/**
* 预约相关API
*/
export default {
/**
* 创建预约
* @param {Object} data 预约信息
*/
createAppointment(data) {
return request({
url: '/appointment/create',
method: 'post',
data
});
},
/**
* 确认预约
* @param {Number} id 预约ID
*/
confirmAppointment(id) {
return request({
url: `/appointment/confirm/${id}`,
method: 'post'
});
},
/**
* 取消预约
* @param {Number} id 预约ID
*/
cancelAppointment(id) {
return request({
url: `/appointment/cancel/${id}`,
method: 'post'
});
},
/**
* 完成预约
* @param {Number} id 预约ID
*/
completeAppointment(id) {
return request({
url: `/appointment/complete/${id}`,
method: 'post'
});
},
/**
* 获取预约详情
* @param {Number} id 预约ID
*/
getAppointmentDetail(id) {
return request({
url: `/appointment/detail/${id}`,
method: 'get'
});
},
/**
* 获取用户的预约列表
*/
getUserAppointments() {
return request({
url: '/appointment/user',
method: 'get'
});
},
/**
* 获取房东的预约列表
*/
getLandlordAppointments() {
return request({
url: '/appointment/landlord',
method: 'get'
});
},
/**
* 获取房源的预约列表
* @param {Number} houseId 房源ID
*/
getHouseAppointments(houseId) {
return request({
url: `/appointment/house/${houseId}`,
method: 'get'
});
},
/**
* 获取指定日期范围内的预约
* @param {Date} startDate 开始日期
* @param {Date} endDate 结束日期
*/
getAppointmentsByDateRange(startDate, endDate) {
return request({
url: '/appointment/date-range',
method: 'get',
params: {
startDate,
endDate
}
});
},
/**
* 获取预约统计数据
*/
getAppointmentStatistics() {
return request({
url: '/appointment/statistics',
method: 'get'
});
}
};
File: d:\htsun\Docs\资料\文章\rental-system\frontend\src\api\feedback.js
javascript
CopyInsert
import request from '@/utils/request';
/**
* 看房反馈相关API
*/
export default {
/**
* 提交看房反馈
* @param {Object} data 反馈信息
*/
submitFeedback(data) {
return request({
url: '/feedback/submit',
method: 'post',
data
});
},
/**
* 更新看房反馈
* @param {Number} id 反馈ID
* @param {Object} data 反馈信息
*/
updateFeedback(id, data) {
return request({
url: `/feedback/update/${id}`,
method: 'put',
data
});
},
/**
* 删除看房反馈
* @param {Number} id 反馈ID
*/
deleteFeedback(id) {
return request({
url: `/feedback/delete/${id}`,
method: 'delete'
});
},
/**
* 设置反馈是否公开
* @param {Number} id 反馈ID
* @param {Number} isPublic 是否公开:0-私密,1-公开
*/
setFeedbackPublic(id, isPublic) {
return request({
url: `/feedback/public/${id}`,
method: 'put',
params: {
isPublic
}
});
},
/**
* 获取反馈详情
* @param {Number} id 反馈ID
*/
getFeedbackDetail(id) {
return request({
url: `/feedback/detail/${id}`,
method: 'get'
});
},
/**
* 获取用户的反馈列表
*/
getUserFeedbacks() {
return request({
url: '/feedback/user',
method: 'get'
});
},
/**
* 获取房源的公开反馈列表
* @param {Number} houseId 房源ID
*/
getHousePublicFeedbacks(houseId) {
return request({
url: `/feedback/house/${houseId}`,
method: 'get'
});
},
/**
* 获取预约的反馈
* @param {Number} appointmentId 预约ID
*/
getAppointmentFeedback(appointmentId) {
return request({
url: `/feedback/appointment/${appointmentId}`,
method: 'get'
});
}
};
File: d:\htsun\Docs\资料\文章\rental-system\frontend\src\api\review.js
javascript
CopyInsert
import request from '@/utils/request';
/**
* 房源评价相关API
*/
export default {
/**
* 提交房源评价
* @param {Object} data 评价信息
*/
submitReview(data) {
return request({
url: '/review/submit',
method: 'post',
data
});
},
/**
* 更新房源评价
* @param {Number} id 评价ID
* @param {Object} data 评价信息
*/
updateReview(id, data) {
return request({
url: `/review/update/${id}`,
method: 'put',
data
});
},
/**
* 删除房源评价
* @param {Number} id 评价ID
*/
deleteReview(id) {
return request({
url: `/review/delete/${id}`,
method: 'delete'
});
},
/**
* 获取评价详情
* @param {Number} id 评价ID
*/
getReviewDetail(id) {
return request({
url: `/review/detail/${id}`,
method: 'get'
});
},
/**
* 获取房源的评价列表
* @param {Number} houseId 房源ID
*/
getHouseReviews(houseId) {
return request({
url: `/review/house/${houseId}`,
method: 'get'
});
},
/**
* 获取用户的评价列表
*/
getUserReviews() {
return request({
url: '/review/user',
method: 'get'
});
},
/**
* 获取房源评价统计数据
* @param {Number} houseId 房源ID
*/
getHouseReviewStatistics(houseId) {
return request({
url: `/review/statistics/${houseId}`,
method: 'get'
});
},
/**
* 检查用户是否已评价房源
* @param {Number} houseId 房源ID
*/
checkUserReviewed(houseId) {
return request({
url: `/review/check/${houseId}`,
method: 'get'
});
},
/**
* 提交评价回复
* @param {Object} data 回复信息
*/
submitReply(data) {
return request({
url: '/reply/submit',
method: 'post',
data
});
},
/**
* 更新评价回复
* @param {Number} id 回复ID
* @param {Object} data 回复信息
*/
updateReply(id, data) {
return request({
url: `/reply/update/${id}`,
method: 'put',
data
});
},
/**
* 删除评价回复
* @param {Number} id 回复ID
*/
deleteReply(id) {
return request({
url: `/reply/delete/${id}`,
method: 'delete'
});
},
/**
* 获取回复详情
* @param {Number} id 回复ID
*/
getReplyDetail(id) {
return request({
url: `/reply/detail/${id}`,
method: 'get'
});
},
/**
* 获取评价的回复列表
* @param {Number} reviewId 评价ID
*/
getReviewReplies(reviewId) {
return request({
url: `/reply/review/${reviewId}`,
method: 'get'
});
}
};
3.2 Vue Components
File: d:\htsun\Docs\资料\文章\rental-system\frontend\src\views\appointment\AppointmentForm.vue
vue
CopyInsert
<template>
<div class="appointment-form-container">
<el-card class="appointment-card">
<div slot="header" class="card-header">
<span>预约看房</span>
</div>
<el-form ref="appointmentForm" :model="appointmentForm" :rules="rules" label-width="100px">
<el-form-item label="联系人" prop="contactName">
<el-input v-model="appointmentForm.contactName" placeholder="请输入联系人姓名"></el-input>
</el-form-item>
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="appointmentForm.contactPhone" placeholder="请输入联系电话"></el-input>
</el-form-item>
<el-form-item label="预约时间" prop="appointmentTime">
<el-date-picker
v-model="appointmentForm.appointmentTime"
type="datetime"
placeholder="选择预约时间"
:picker-options="pickerOptions"
value-format="yyyy-MM-dd HH:mm:ss"
format="yyyy-MM-dd HH:mm">
</el-date-picker>
</el-form-item>
<el-form-item label="预约备注" prop="appointmentNotes">
<el-input
v-model="appointmentForm.appointmentNotes"
type="textarea"
:rows="4"
placeholder="请输入预约备注,如特殊要求等">
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitAppointment" :loading="loading">提交预约</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
import appointmentApi from '@/api/appointment';
import { formatDate } from '@/utils/date';
export default {
name: 'AppointmentForm',
props: {
houseId: {
type: [Number, String],
required: true
}
},
data() {
// 验证手机号
const validatePhone = (rule, value, callback) => {
if (!/^1[3-9]\d{9}$/.test(value)) {
callback(new Error('请输入正确的手机号码'));
} else {
callback();
}
};
// 验证预约时间
const validateAppointmentTime = (rule, value, callback) => {
if (!value) {
callback(new Error('请选择预约时间'));
return;
}
const now = new Date();
const appointmentTime = new Date(value);
if (appointmentTime <= now) {
callback(new Error('预约时间必须是将来的时间'));
return;
}
callback();
};
return {
appointmentForm: {
houseId: this.houseId,
contactName: '',
contactPhone: '',
appointmentTime: '',
appointmentNotes: ''
},
rules: {
contactName: [
{ required: true, message: '请输入联系人姓名', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
contactPhone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ validator: validatePhone, trigger: 'blur' }
],
appointmentTime: [
{ required: true, message: '请选择预约时间', trigger: 'change' },
{ validator: validateAppointmentTime, trigger: 'change' }
]
},
pickerOptions: {
disabledDate(time) {
return time.getTime() < Date.now() - 8.64e7; // 禁用今天之前的日期
},
selectableRange: '09:00:00 - 18:00:00' // 可选时间范围
},
loading: false
};
},
methods: {
submitAppointment() {
this.$refs.appointmentForm.validate(valid => {
if (valid) {
this.loading = true;
appointmentApi.createAppointment(this.appointmentForm)
.then(response => {
this.$message.success('预约提交成功,请等待房东确认');
this.$emit('success', response.data);
this.resetForm();
})
.catch(error => {
this.$message.error(error.message || '预约提交失败,请稍后重试');
})
.finally(() => {
this.loading = false;
});
} else {
return false;
}
});
},
resetForm() {
this.$refs.appointmentForm.resetFields();
}
}
};
</script>
<style scoped>
.appointment-form-container {
max-width: 600px;
margin: 0 auto;
}
.appointment-card {
margin-bottom: 20px;
}
.card-header {
font-size: 18px;
font-weight: bold;
}
</style>
File: d:\htsun\Docs\资料\文章\rental-system\frontend\src\views\appointment\AppointmentList.vue
vue
CopyInsert
<template>
<div class="appointment-list-container">
<el-card class="appointment-card">
<div slot="header" class="card-header">
<span>我的预约</span>
<el-radio-group v-model="activeTab" size="small" @change="handleTabChange">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="pending">待确认</el-radio-button>
<el-radio-button label="confirmed">已确认</el-radio-button>
<el-radio-button label="completed">已完成</el-radio-button>
<el-radio-button label="canceled">已取消</el-radio-button>
</el-radio-group>
</div>
<div class="filter-container">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
@change="handleDateRangeChange">
</el-date-picker>
<el-button type="primary" icon="el-icon-search" @click="fetchAppointments">查询</el-button>
</div>
<el-table
v-loading="loading"
:data="appointmentList"
style="width: 100%"
@row-click="handleRowClick">
<el-table-column prop="id" label="预约编号" width="100"></el-table-column>
<el-table-column label="房源信息" min-width="200">
<template slot-scope="scope">
<div v-if="scope.row.house">{{ scope.row.house.title }}</div>
<div v-else>房源信息加载中...</div>
</template>
</el-table-column>
<el-table-column label="预约时间" width="180">
<template slot-scope="scope">
{{ formatDate(scope.row.appointmentTime) }}
</template>
</el-table-column>
<el-table-column label="联系人" width="120">
<template slot-scope="scope">
{{ scope.row.contactName }}<br>
{{ scope.row.contactPhone }}
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template slot-scope="scope">
<el-tag :type="getStatusType(scope.row.status)">{{ getStatusText(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button
v-if="scope.row.status === 0"
size="mini"
type="danger"
@click.stop="handleCancel(scope.row)">取消预约</el-button>
<el-button
v-if="scope.row.status === 3 && !scope.row.hasFeedback"
size="mini"
type="primary"
@click.stop="handleFeedback(scope.row)">提交反馈</el-button>
<el-button
size="mini"
type="info"
@click.stop="handleDetail(scope.row)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</el-card>
<!-- 反馈表单对话框 -->
<el-dialog title="看房反馈" :visible.sync="feedbackDialogVisible" width="600px">
<el-form ref="feedbackForm" :model="feedbackForm" :rules="feedbackRules" label-width="100px">
<el-form-item label="满意度" prop="satisfactionLevel">
<el-rate
v-model="feedbackForm.satisfactionLevel"
:colors="['#99A9BF', '#F7BA2A', '#FF9900']"
:texts="['失望', '一般', '满意', '很满意', '非常满意']"
show-text>
</el-rate>
</el-form-item>
<el-form-item label="反馈内容" prop="feedbackContent">
<el-input
v-model="feedbackForm.feedbackContent"
type="textarea"
:rows="6"
placeholder="请详细描述您的看房体验,包括房源状况、交通便利性、周边环境等">
</el-input>
</el-form-item>
<el-form-item label="是否公开">
<el-switch
v-model="feedbackForm.isPublic"
:active-value="1"
:inactive-value="0"
active-text="公开"
inactive-text="私密">
</el-switch>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="feedbackDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitFeedback" :loading="feedbackLoading">提交</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import appointmentApi from '@/api/appointment';
import feedbackApi from '@/api/feedback';
import houseApi from '@/api/house';
import { formatDate } from '@/utils/date';
export default {
name: 'AppointmentList',
data() {
return {
activeTab: 'all',
appointmentList: [],
loading: false,
currentPage: 1,
pageSize: 10,
total: 0,
dateRange: null,
// 反馈相关
feedbackDialogVisible: false,
feedbackLoading: false,
currentAppointment: null,
feedbackForm: {
appointmentId: null,
satisfactionLevel: 3,
feedbackContent: '',
isPublic: 1
},
feedbackRules: {
satisfactionLevel: [
{ required: true, message: '请选择满意度', trigger: 'change' }
],
feedbackContent: [
{ required: true, message: '请输入反馈内容', trigger: 'blur' },
{ min: 10, max: 500, message: '长度在 10 到 500 个字符', trigger: 'blur' }
]
}
};
},
created() {
this.fetchAppointments();
},
methods: {
formatDate,
getStatusType(status) {
const typeMap = {
0: 'info', // 待确认
1: 'warning', // 已确认
2: 'danger', // 已取消
3: 'success' // 已完成
};
return typeMap[status] || 'info';
},
getStatusText(status) {
const textMap = {
0: '待确认',
1: '已确认',
2: '已取消',
3: '已完成'
};
return textMap[status] || '未知';
},
fetchAppointments() {
this.loading = true;
let promise;
if (this.dateRange && this.dateRange.length === 2) {
promise = appointmentApi.getAppointmentsByDateRange(this.dateRange[0], this.dateRange[1]);
} else {
promise = appointmentApi.getUserAppointments();
}
promise.then(response => {
let appointments = response.data || [];
// 根据标签过滤
if (this.activeTab !== 'all') {
const statusMap = {
'pending': 0,
'confirmed': 1,
'canceled': 2,
'completed': 3
};
appointments = appointments.filter(item => item.status === statusMap[this.activeTab]);
}
this.total = appointments.length;
// 分页处理
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
this.appointmentList = appointments.slice(start, end);
// 加载房源信息
this.loadHouseInfo();
// 检查是否已提交反馈
this.checkFeedback();
}).catch(error => {
this.$message.error(error.message || '获取预约列表失败');
}).finally(() => {
this.loading = false;
});
},
loadHouseInfo() {
const houseIds = this.appointmentList.map(item => item.houseId);
houseIds.forEach(houseId => {
houseApi.getHouseDetail(houseId).then(response => {
const house = response.data;
this.appointmentList.forEach(item => {
if (item.houseId === houseId) {
this.$set(item, 'house', house);
}
});
}).catch(() => {
// 忽略错误
});
});
},
checkFeedback() {
const completedAppointments = this.appointmentList.filter(item => item.status === 3);
completedAppointments.forEach(appointment => {
feedbackApi.getAppointmentFeedback(appointment.id).then(response => {
if (response.code === 200) {
this.$set(appointment, 'hasFeedback', true);
} else {
this.$set(appointment, 'hasFeedback', false);
}
}).catch(() => {
this.$set(appointment, 'hasFeedback', false);
});
});
},
handleTabChange() {
this.currentPage = 1;
this.fetchAppointments();
},
handleDateRangeChange() {
this.currentPage = 1;
},
handleSizeChange(val) {
this.pageSize = val;
this.fetchAppointments();
},
handleCurrentChange(val) {
this.currentPage = val;
this.fetchAppointments();
},
handleRowClick(row) {
this.handleDetail(row);
},
handleDetail(row) {
this.$router.push(`/appointment/detail/${row.id}`);
},
handleCancel(row) {
this.$confirm('确定要取消此预约吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
appointmentApi.cancelAppointment(row.id).then(() => {
this.$message.success('预约已取消');
this.fetchAppointments();
}).catch(error => {
this.$message.error(error.message || '取消预约失败');
});
}).catch(() => {
// 取消操作
});
},
handleFeedback(row) {
this.currentAppointment = row;
this.feedbackForm.appointmentId = row.id;
this.feedbackForm.satisfactionLevel = 3;
this.feedbackForm.feedbackContent = '';
this.feedbackForm.isPublic = 1;
this.feedbackDialogVisible = true;
},
submitFeedback() {
this.$refs.feedbackForm.validate(valid => {
if (valid) {
this.feedbackLoading = true;
feedbackApi.submitFeedback(this.feedbackForm).then(() => {
this.$message.success('反馈提交成功');
this.feedbackDialogVisible = false;
this.fetchAppointments();
}).catch(error => {
this.$message.error(error.message || '反馈提交失败');
}).finally(() => {
this.feedbackLoading = false;
});
} else {
return false;
}
});
}
}
};
</script>
<style scoped>
.appointment-list-container {
padding: 20px;
}
.appointment-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.filter-container {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
</style>
File: d:\htsun\Docs\资料\文章\rental-system\frontend\src\views\appointment\AppointmentDetail.vue
vue
CopyInsert
<template>
<div class="appointment-detail-container">
<el-card class="appointment-card" v-loading="loading">
<div slot="header" class="card-header">
<span>预约详情</span>
<el-button icon="el-icon-back" @click="goBack">返回</el-button>
</div>
<div v-if="appointment" class="appointment-info">
<el-descriptions :column="2" border>
<el-descriptions-item label="预约编号">{{ appointment.id }}</el-descriptions-item>
<el-descriptions-item label="预约状态">
<el-tag :type="getStatusType(appointment.status)">{{ getStatusText(appointment.status) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="预约时间">{{ formatDate(appointment.appointmentTime) }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ formatDate(appointment.createTime) }}</el-descriptions-item>
<el-descriptions-item label="联系人">{{ appointment.contactName }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ appointment.contactPhone }}</el-descriptions-item>
<el-descriptions-item label="预约备注" :span="2">
{{ appointment.appointmentNotes || '无' }}
</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="house" class="house-info">
<h3>房源信息</h3>
<el
The model produced an invalid tool call, trying again.
No credits consumed on this tool call.
Erroran internal error occurred (error ID: 0bf2dd709b094428b66199d732f6548e)
DoneFeedback has been submitted
Me
补全不完整的代码, 及遗漏的文件。
Let me complete the unfinished AppointmentDetail.vue file and provide the remaining frontend components for the appointment module:
Completing the AppointmentDetail.vue file
File: d:\htsun\Docs\资料\文章\rental-system\frontend\src\views\appointment\AppointmentDetail.vue (continuation)
vue
CopyInsert
<el-card class="house-card" shadow="hover">
<div class="house-title">{{ house.title }}</div>
<div class="house-info-grid">
<div class="info-item">
<span class="label">价格:</span>
<span class="value">{{ house.price }}元/月</span>
</div>
<div class="info-item">
<span class="label">面积:</span>
<span class="value">{{ house.area }}㎡</span>
</div>
<div class="info-item">
<span class="label">户型:</span>
<span class="value">{{ house.houseType }}</span>
</div>
<div class="info-item">
<span class="label">朝向:</span>
<span class="value">{{ house.orientation }}</span>
</div>
<div class="info-item">
<span class="label">楼层:</span>
<span class="value">{{ house.floor }}</span>
</div>
<div class="info-item">
<span class="label">装修:</span>
<span class="value">{{ house.decoration }}</span>
</div>
</div>
<div class="house-address">
<span class="label">地址:</span>
<span class="value">{{ house.community }} {{ house.address }}</span>
</div>
<div class="house-contact">
<span class="label">联系人:</span>
<span class="value">{{ house.contact }} {{ house.contactPhone }}</span>
</div>
</el-card>
</div>
<div v-if="feedback" class="feedback-info">
<h3>看房反馈</h3>
<el-card class="feedback-card" shadow="hover">
<div class="feedback-header">
<el-rate
v-model="feedback.satisfactionLevel"
disabled
show-score
text-color="#ff9900">
</el-rate>
<span class="feedback-time">{{ formatDate(feedback.createTime) }}</span>
</div>
<div class="feedback-content">{{ feedback.feedbackContent }}</div>
</el-card>
</div>
<div class="action-buttons" v-if="appointment">
<el-button
v-if="appointment.status === 0"
type="danger"
@click="handleCancel">取消预约</el-button>
<el-button
v-if="isLandlord && appointment.status === 0"
type="primary"
@click="handleConfirm">确认预约</el-button>
<el-button
v-if="isLandlord && appointment.status === 1"
type="success"
@click="handleComplete">完成预约</el-button>
<el-button
v-if="appointment.status === 3 && !feedback"
type="primary"
@click="handleFeedback">提交反馈</el-button>
</div>
<!-- 反馈表单对话框 -->
<el-dialog title="看房反馈" :visible.sync="feedbackDialogVisible" width="600px">
<el-form ref="feedbackForm" :model="feedbackForm" :rules="feedbackRules" label-width="100px">
<el-form-item label="满意度" prop="satisfactionLevel">
<el-rate
v-model="feedbackForm.satisfactionLevel"
:colors="['#99A9BF', '#F7BA2A', '#FF9900']"
:texts="['失望', '一般', '满意', '很满意', '非常满意']"
show-text>
</el-rate>
</el-form-item>
<el-form-item label="反馈内容" prop="feedbackContent">
<el-input
v-model="feedbackForm.feedbackContent"
type="textarea"
:rows="6"
placeholder="请详细描述您的看房体验,包括房源状况、交通便利性、周边环境等">
</el-input>
</el-form-item>
<el-form-item label="是否公开">
<el-switch
v-model="feedbackForm.isPublic"
:active-value="1"
:inactive-value="0"
active-text="公开"
inactive-text="私密">
</el-switch>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="feedbackDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitFeedback" :loading="feedbackLoading">提交</el-button>
</div>
</el-dialog>
</el-card>
</div>
</template>
<script>
import appointmentApi from '@/api/appointment';
import feedbackApi from '@/api/feedback';
import houseApi from '@/api/house';
import { formatDate } from '@/utils/date';
import { mapGetters } from 'vuex';
export default {
name: 'AppointmentDetail',
data() {
return {
loading: false,
appointment: null,
house: null,
feedback: null,
// 反馈相关
feedbackDialogVisible: false,
feedbackLoading: false,
feedbackForm: {
appointmentId: null,
satisfactionLevel: 3,
feedbackContent: '',
isPublic: 1
},
feedbackRules: {
satisfactionLevel: [
{ required: true, message: '请选择满意度', trigger: 'change' }
],
feedbackContent: [
{ required: true, message: '请输入反馈内容', trigger: 'blur' },
{ min: 10, max: 500, message: '长度在 10 到 500 个字符', trigger: 'blur' }
]
}
};
},
computed: {