我的房源列表页
用户进入我的房源页面时, 应该展示该用户发布的房源列表信息
房源列表后端逻辑
需要给前端返回房屋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,
}
注:
- 创建时间需要从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>
注:
-
模板代码需要使用
<script type="text/html" id=
xxxx>
的标签包裹, 整段script
脚本可以放在任意位置, 不过最好是哪里需要编写模板就放在哪里 -
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')
注:
template
函数的第一个参数为html模板中script
标签的ID属性, 第二个参数必须是一个js对象, key为给html模板传入的参数名, val为传入的参数值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
}
注:
- 一个房源中允许存在多条图片/设施/评论信息, 因此使用列表生成式遍历反向关系对象, 再获取相应的属性
- 在获取评论时, 需要从该房屋关联的订单中获取, 且需要获取评论人/时间/评论内容, 所以把这个过程分为两步:
- 首先获取到相关的订单, 需要限制订单的状态和评论是否为空, 并且一般都是把最新的评论展示在前面, 因此需要按最后更新日期倒序, 最终获取的是订单对象列表.
- 通过列表生成式遍历订单对象列表, 获取订单的评论相关的信息, 再生成一个新的评论内容列表.
编写详情页接口
在房屋视图文件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'}
注:
- 由于房屋详细内容很少改变且访问频率比较高, 所以使用redis缓存这些信息, 存入的数据就是转换后的json字符串, 可能是由于内容中包含中文, 存入的是编码后的数据, 所以取出时需要手动解码一下.
- 这里将当前登录用户user_id和房东owner_id都传给了前端, 前端根据这两个值判断是否显示预定按钮
- 由于
info_json
本身就已经是字符串类型了, 因此最终返回时采用元组的形式返回, 第一个是返回的json字符串, 第二个是http状态码, 第三个是headers(这里需要指定返回的类型为application/json
) - 其中第一个值需要再加上
errno
和data
键值对, 因此需要格式化拼接字符串, 在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');
};
})
注:
-
template
的第二个参数为js对象, 其中可以包含多个键值对 -
轮播图插件
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文本也并没有生成, 所以轮播效果就没有了.
-