1. 程式人生 > 實用技巧 >Flask-愛家租房專案ihome-07-我的房源列表頁和房源詳情頁

Flask-愛家租房專案ihome-07-我的房源列表頁和房源詳情頁

我的房源列表頁

使用者進入我的房源頁面時, 應該展示該使用者釋出的房源列表資訊

房源列表後端邏輯

需要給前端返回房屋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

房屋詳情頁後端邏輯

模型類中新增統一返回資訊的邏輯

與我的房源列表頁類似, 將前端需要展示的房屋資訊傳送過去就好了, 同樣在房屋的模型類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):
    # 查詢快取中是否存在資料
    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()
        # 獲取當前登入使用者, 為空則說明未登入
        user_id = session.get('user_id', '-1')
        # 將當前使用者加入房屋詳情中
        info['user_id'] = user_id
        # 將字典轉為json
        info_json = json.dumps(info)
        # 存入快取中
        redis_connect.setex(redis_key, constants.HOUSE_REDIS_EXPIRES, info_json)

    return f'{{"errno": 0, "data": {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 house = resp.data;
                //使用template設定圖片
                $('.swiper-container').html(template('house-images', {houseImages: house.img_urls, price: house.price}))
                //使用template設定其他資訊
                $('.detail-con').html(template('house-info', {house: house}))
                //不是當前使用者不是房東則展示即刻預定按鈕
                if (house.user_id != house.owner_id) {
                    $(".book-house").show();
                }
                //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請求外部, 就發現沒有輪播的效果了.