Java全栈项目-大学生租房管理系统(6)

代码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: {
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值