Flask-爱家租房项目ihome-07-我的房源列表页和房源详情页

我的房源列表页

用户进入我的房源页面时, 应该展示该用户发布的房源列表信息

image-20200827225636224

房源列表后端逻辑

需要给前端返回房屋ID/标题/城区/价格/发布时间/默认图片的数据, 可以在接口中一个一个查询返回, 也可以在房屋的模型类Houses中添加一个方法get_list_info用来汇总维护这些字段信息.

# ihome/models.py
class Houses(BasicModel):
    """房屋模型类"""
    __tablename__ = 'ih_houses'
    ......
    # 房屋列表页的信息
    def get_list_info(self):
        return {
            'house_id': self.id,
            'title': self.title,
            'area_name': self.area.name,
            'price': self.price,
            'created_date': datetime.strftime(self.created_date, '%Y-%m-%d %H:%M:%S'),
            'img_url': self.default_image_url,
        }

注:

  1. 创建时间需要从datetime类型转化为字符串类型

在房屋视图文件ihome/api_1_0/houses.py中, 添加返回房屋列表信息的后端接口, url为: api/v1.0/user/houses

@api.route('/user/houses')
@login_required
def get_user_houses():
    """返回我的房源列表信息"""
    user = g.user
    # 获取该用户下的房屋对象列表
    try:
        house_objs = user.houses
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='获取房屋信息异常')
    # 获取房屋列表页需要展示的信息
    houses = [obj.get_list_info() for obj in house_objs]
    return jsonify(errno=RET.OK, data=houses)

房屋列表前端逻辑

后端返回的json格式为[{"房屋1": xxxx},{"房屋2": xxxx},{"房屋3": xxxx}], 所以前端需要遍历整个列表, 把每个房屋的信息提取出来, 并展示. 目前最好的方法还是使用前端模板, 因为在模板中可以循环数据并展示. 继续使用art-template模板插件

编辑html文件

首先编辑对应的html文件myhouse.html, 在展示房屋列表处添加模板代码

<ul id="houses-list" class="houses-list">
    <li>
        <div class="new-house">
            <a href="/newhouse.html">发布新房源</a>
        </div>
    </li>
    <script type="text/html" id="list-house-info">
        {{ each houses as house}}
            <li>
                <a href="/detail.html?id={{house.house_id}}">
                    <div class="house-title">
                        <h3>房屋ID:{{ house.house_id }} —— {{ house.title }}</h3>
                    </div>
                    <div class="house-content">
                        <img src="{{ house.img_url }}">
                        <div class="house-text">
                            <ul>
                                <li>位于:{{ house.area_name }}</li>
                                <li>价格:¥{{ house.price }}/晚</li>
                                <li>发布时间:{{ house.created_date }}</li>
                            </ul>
                        </div>
                    </div>
                </a>
            </li>
        {{ /each }}
    </script>
</ul>

注:

  1. 模板代码需要使用<script type="text/html" id=xxxx>的标签包裹, 整段script脚本可以放在任意位置, 不过最好是哪里需要编写模板就放在哪里

  2. art-template的语法中使用each可以遍历列表和js对象, houses为js逻辑处理返回给模板的参数, 使用as关键字可以给里面的元素重命名, 使用起来比较方便, 也可以使用{{$index}}表示下标索引 {{$value}}表示值

    {{each target}}
        {{$index}} {{$value}}
    {{/each}}
    

编辑js文件

编辑对应的js文件myhouse.js, 添加发送请求和处理模板的代码

//发送ajax请求获取我的房屋信息
$.get('api/v1.0/user/houses', function (resp) {
    if (resp.errno == '0'){
        //使用art-template模板发送房屋信息, 获取html文本
        var html = template('list-house-info', {houses:resp.data})
        //将html文本放到合适的位置
        $('.houses-list').append(html)
    }
}, 'json')

注:

  1. template函数的第一个参数为html模板中script标签的ID属性, 第二个参数必须是一个js对象, key为给html模板传入的参数名, val为传入的参数值
  2. template函数返回的是一个html文本, 因此需要通过jQuery把该文本设置到模板想要放入的位置

房源详情页

在我的房源列表也中点击具体的房源标题, 即可进入房源详情页, 对应的url为: /detail.html?id=房屋ID

image-20200827170652568

房屋详情页后端逻辑

模型类中添加统一返回信息的逻辑

与我的房源列表页类似, 将前端需要展示的房屋信息发送过去就好了, 同样在房屋的模型类Houses中添加统一返回详情的方法.

# ihome/models.py
class Houses(BasicModel):
    """房屋模型类"""
    __tablename__ = 'ih_houses'
    ......
    # 房屋详细信息
    def get_detail_info(self):
        """获取房屋详细信息"""
        return {
            'img_urls': [image.image_url for image in self.images],
            'title': self.title,
            'price': self.price,
            'owner_id': self.user.id,
            'owner_img_url': self.user.image_url,
            'owner_name': self.user.name,
            'address': self.address,
            'room_count': self.room_count,
            'acreage': self.acreage,
            'unit': self.unit,
            'capacity': self.capacity,
            'beds': self.beds,
            'deposit': self.deposit,
            'min_days': self.min_days,
            'max_days': self.max_days,
            'facilities': [facility.id for facility in self.facilities],
            'comments': [order.get_comment() for order in self.get_comment_orders()],
        }
    
    def get_comment_orders(self):
        """获取房屋评论过的订单"""
        # 房屋ID当前房屋的/订单状态为已完成的/评论内容不为空的/根据最后更新时间倒叙排序/获取前10条评论展示
        orders = Orders.query.filter(Orders.house_id == self.id, Orders.status == 'COMPLETED',
                                     Orders.comment is not None).order_by(Orders.updated_date.desc()).limit(
            COMMENT_DISPLAY_COUNTS).all()
        return orders

# 订单模型类
class Orders(BasicModel):
    """订单模型类"""
    __tablename__ = 'ih_orders'
    ......
    # 获取评论信息
    def get_comment(self):
        """获取评论信息"""
        return {
            'user_name': self.user.name if self.user.name != self.user.phone else '匿名用户',
            'comment_date': datetime.strftime(self.updated_date, '%Y-%m-%d %H:%M:%S'),
            'comment': self.comment
        }

注:

  1. 一个房源中允许存在多条图片/设施/评论信息, 因此使用列表生成式遍历反向关系对象, 再获取相应的属性
  2. 在获取评论时, 需要从该房屋关联的订单中获取, 且需要获取评论人/时间/评论内容, 所以把这个过程分为两步:
    • 首先获取到相关的订单, 需要限制订单的状态和评论是否为空, 并且一般都是把最新的评论展示在前面, 因此需要按最后更新日期倒序, 最终获取的是订单对象列表.
    • 通过列表生成式遍历订单对象列表, 获取订单的评论相关的信息, 再生成一个新的评论内容列表.

编写详情页接口

在房屋视图文件house.py中添加返回详情页信息的接口, url为: api/v1.0/houses/房屋ID

@api.route('/houses/<int:house_id>')
def get_house_info(house_id):
    # 获取当前登录用户, 为-1则说明未登录
    user_id = session.get('user_id', '-1')
    # 查询缓存中是否存在数据
    redis_key = f'house_info_{house_id}'
    try:
        info_json = redis_connect.get(redis_key).decode()
    except Exception as e:
        current_app.logger.error(e)
        info_json = None
    # 缓存中不存在则查询房屋信息
    if not info_json:
        try:
            house = Houses.query.get(house_id)
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg='获取房屋信息异常')
        if not house:
            return jsonify(errno=RET.NODATA, errmsg='房屋ID不存在')
        # 获取房屋详情
        info = house.get_detail_info()
        # 将字典转为json
        info_json = json.dumps(info)
        # 存入缓存中
        redis_connect.setex(redis_key, constants.HOUSE_REDIS_EXPIRES, info_json)

    return f'{{"errno": 0, "data": {{"user_id": {user_id}, "house": {info_json}}}}}', 200, {'Content-Type': 'application/json'}

注:

  1. 由于房屋详细内容很少改变且访问频率比较高, 所以使用redis缓存这些信息, 存入的数据就是转换后的json字符串, 可能是由于内容中包含中文, 存入的是编码后的数据, 所以取出时需要手动解码一下.
  2. 这里将当前登录用户user_id和房东owner_id都传给了前端, 前端根据这两个值判断是否显示预定按钮
  3. 由于info_json本身就已经是字符串类型了, 因此最终返回时采用元组的形式返回, 第一个是返回的json字符串, 第二个是http状态码, 第三个是headers(这里需要指定返回的类型为application/json)
  4. 其中第一个值需要再加上errnodata键值对, 因此需要格式化拼接字符串, 在f-string的格式化中, 一对大括号表示引用变量, 两对大括号{{ }}表示一对大括号.

房屋详情页前端逻辑

由于需要展示的房屋信息很多, 不好去用jQuery一个一个获取标签然后设置值, 因此还是使用了前端模板art-template

修改html文件, 添加模板

编辑对应的html文件detail.html, 在展示房屋图片和房屋具体信息处添加模板代码

<div class="swiper-container">
    <script type="text/html" id="house-images">
        <div class="swiper-pagination"></div>
        <ul class="swiper-wrapper">
            {{ each houseImages as image }}
            <li class="swiper-slide"><img src="{{ image }}"></li>
            {{ /each }}
        </ul>
        <div class="house-price">¥<span>{{ price }}</span>/晚</div>
    </script>
</div>
......
<div class="detail-con">
    <script type="text/html" id="house-info">
        <div class="detail-header layout-style">
            <h2 class="house-title">{{ house.title }}</h2>
            <div class="landlord-pic"><img src="{{ house.owner_img_url }}"></div>
            <h2 class="landlord-name">房东: <span>{{ house.owner_name }}</span></h2>
        </div>
        <div class="house-info layout-style">
           <h3>房屋地址</h3>
           <ul class="house-info-list text-center">
                <li>{{ house.address }}</li>
           </ul>
        </div>
        <ul class="house-type layout-style">
            <li>
                <span class="icon-house"></span>
                <div class="icon-text">
                    <h3>出租{{ house.room_count }}间</h3>
                    <p>房屋面积:{{ house.acreage }}平米</p>
                    <p>房屋户型:{{ house.unit }}</p>
                </div>
            </li>
            <li>
                <span class="icon-user"></span>
                <div class="icon-text">
                    <h3>宜住{{ house.capacity }}人</h3>
                </div>
            </li>
            <li>
                <span class="icon-bed"></span>
                <div class="icon-text">
                    <h3>卧床配置</h3>
                    <p>{{ house.beds }}</p>
                </div>
            </li>
        </ul>
        <div class="house-info layout-style">
            <h3>房间详情</h3>
            <ul class="house-info-list">
                <li>收取押金<span>{{ house.deposit }}</span></li>
                <li>最少入住天数<span>{{ house.min_days }}</span></li>
                <li>最多入住天数<span>{{ if house.max_days>=0 }}{{ house.max_days }}{{ else }}无限制{{ /if }}</span></li>
            </ul>
        </div>
        <div class="house-facility layout-style">
            <h3>配套设施</h3>
            <ul class="house-facility-list clearfix">
                <li><span class="{{ if house.facilities.indexOf(1)>=0 }}wirelessnetwork-ico {{ else }}jinzhi-ico{{ /if }}"></span>无线网络</li>
                <li><span class="{{ if house.facilities.indexOf(2)>=0 }}shower-ico {{ else }}jinzhi-ico{{ /if }}"></span>热水淋浴</li>
.........
            </ul>
        </div>
        {{ if house.comments.length != 0 }}
        <div class="house-info layout-style">
            <h3>评价信息</h3>
            <ul class="house-comment-list">
                {{ each house.comments as comment }}
                <li>
                    <p>用户: {{ comment.user_name }}<span class="fr">{{ comment.comment_date }}</span></p>
                    <p>评论内容: {{ comment.comment }}</p>
                </li>
                {{ /each }}
            </ul>
        </div>
        {{ /if }}
    </script>
</div>

修改js文件, 添加调用模板代码

编辑对应的js文件detail.js, 添加发送请求和处理模板的代码

$(document).ready(function(){
    // 获取url的参数, 房屋id
    var url_param = decodeQuery()
    // url中存在房屋ID则发送ajax请求获取房屋数据
    if (url_param !== undefined) {
        $.get('api/v1.0/houses/' + url_param.id, function (resp) {
            if (resp.errno == '0') {
                //获取成功
                var data = resp.data;
                //使用template设置图片
                $('.swiper-container').html(template('house-images', {houseImages: data.house.img_urls, price: data.house.price}))
                //展示其他信息
                $('.detail-con').html(template('house-info', {house: data.house}))
                //不是当前用户不是房东则展示即刻预定按钮
                if (data.user_id != data.house.owner_id) {
                    $(".book-house").show();
                };
                
                //设置预定按钮的跳转url
                if (data.user_id != -1){
                    //登录了则进入预定界面
                    var url = '/booking.html?id=' + url_param.id;
                }else{
                    //未登录则进入登录界面
                    var url = '/login.html';
                }
                $('.book-house').attr('href', url);

                //Swiper轮播图插件
                var mySwiper = new Swiper ('.swiper-container', {
                    loop: true,
                    autoplay: 2000,
                    autoplayDisableOnInteraction: false,
                    pagination: '.swiper-pagination',
                    paginationType: 'fraction'
                });
            } else {
                //获取失败
                alert(resp.errmsg)
            }
        }, 'json');
    };
})

注:

  1. template的第二个参数为js对象, 其中可以包含多个键值对

  2. 轮播图插件Swiper和模板插件art-template一起使用时, 注意两点

    • html中导入插件时, 最好都放在底部导入, 且先导入轮播图插件js再导入模板js和业务代码js

      <script src="/static/js/jquery.min.js"></script>
      <script src="/static/plugins/swiper/js/swiper.jquery.min.js"></script>
      <script src="/static/js/template.js"></script>
      <script src="/static/js/ihome/detail.js"></script>
      
    • 在业务代码js文件detail.js中, 创建的Swiper对象必须和调用模板设置的逻辑放在同一个回调函数success中, 如果把创建mySwiper的语句放到ajax请求外部, 就发现没有轮播的效果了. 因为放到请求外部的话, 那么执行完发送ajax请求后(还没有来得及执行回调函数)就会执行下面的创建mySwiper的语句, 而此时回调函数还未执行, html文本也并没有生成, 所以轮播效果就没有了.

原文地址:https://www.cnblogs.com/gcxblogs/p/13574524.html