Feat;旅居评价列表

This commit is contained in:
LinCyJang
2026-01-11 01:03:37 +08:00
parent 6f367223c5
commit 79e23a66cf
4 changed files with 379 additions and 101 deletions
+19
View File
@@ -196,6 +196,25 @@ export function getJiaLouProductDetail(id) {
return request.get('/travel/product/' + id, {}, {login: true}) return request.get('/travel/product/' + id, {}, {login: true})
} }
/**
* 旅居团购产品评论列表
* travelGroupProductId int 旅居团购产品ID
* type int 0-全部 1-好评 2-中评 3-差评
* page int 页码
* limit int 每页数量
*/
export function getJiaLouProductReply(params) {
return request.get('/travel/product/reply', params, {login: true})
}
/**
* 获取旅居团购产品评论统计
* travelGroupProductId int 旅居团购产品ID
*/
export function getJiaLouProductReplyCount(params) {
return request.get('/travel/product/reply/count', params, {login: true})
}
/** /**
* 管家列表 * 管家列表
* merId int 商户ID * merId int 商户ID
+159 -46
View File
@@ -5,64 +5,85 @@
:key="evaluateWtapperIndex" :key="evaluateWtapperIndex"
class="evaluateItem" class="evaluateItem"
> >
<view class="pic-text acea-row row-middle"> <!-- 用户信息头部 -->
<view class="pictrue"> <view class="user-header">
<image :src="item.avatar" class="image" /> <view class="avatar-box">
<image :src="item.avatar" class="avatar" mode="aspectFill" />
</view> </view>
<view class="acea-row row-middle"> <view class="user-info">
<view class="name line1">{{ item.nickname }}</view> <view class="name line1">{{ item.nickname }}</view>
<view class="time">{{ item.createTime }} </view> <view class="time">{{ item.createTime }}</view>
</view> </view>
</view> </view>
<view class="v12-px-4">
<view class="v12-secondary-dark-text v12-font-24 attr-bg"> {{ item.sku }}</view> <!-- 默认标签 -->
<view class="start" :class="'star' + item.star"></view> <view class="tag-row" v-if="item.sku">
<view class="default-tag">{{ item.sku }}</view>
</view> </view>
<view class="evaluate-infor v12-font-24">{{ item.comment }}</view>
<view class="imgList acea-row"> <!-- 评分星星 -->
<view class="star-row">
<u-icon
v-for="n in 5"
:key="n"
:name="n <= item.star ? 'star-fill' : 'star'"
color="#FE5261"
size="28"
class="star-icon"
></u-icon>
</view>
<!-- 评论内容 -->
<view class="evaluate-infor">{{ item.comment }}</view>
<!-- 图片/视频列表 -->
<view class="imgList">
<!-- 视频 -->
<view v-if="item.video" class="pictrue"> <view v-if="item.video" class="pictrue">
<view <view
@click="pauseOtherVideo('video' + evaluateWtapperIndex)" @click="pauseOtherVideo('video' + evaluateWtapperIndex)"
v-if="activeVideo !== 'video' + evaluateWtapperIndex" v-if="activeVideo !== 'video' + evaluateWtapperIndex"
class="play-icon" class="play-icon"
> >
<u-icon name="play-right-fill" color="#fff" size="48"></u-icon> <u-icon name="play-right-fill" color="#fff" size="48"></u-icon>
</view> </view>
<image <image
v-if="activeVideo !== 'video' + evaluateWtapperIndex" v-if="activeVideo !== 'video' + evaluateWtapperIndex"
:src="item.video + '?vframe/jpg/offset/1'" :src="item.video + '?vframe/jpg/offset/1'"
class="image pic-img" class="image"
mode="aspectFill"
@click="pauseOtherVideo('video' + evaluateWtapperIndex)" @click="pauseOtherVideo('video' + evaluateWtapperIndex)"
> />
</image>
<video <video
v-else v-else
:src="item.video" :src="item.video"
:poster="item.video + '?vframe/jpg/offset/1'" :poster="item.video + '?vframe/jpg/offset/1'"
controls controls
autoplay autoplay
play-btn-position="center"
:id="'video' + evaluateWtapperIndex"
class="image" class="image"
:id="'video' + evaluateWtapperIndex"
@play="pauseOtherVideo('video' + evaluateWtapperIndex)" @play="pauseOtherVideo('video' + evaluateWtapperIndex)"
/> />
</view> </view>
<!-- 图片 -->
<view <view
v-for="(itemn, eq) in item.picturesArr" v-for="(pic, idx) in item.picturesArr"
:key="eq" :key="idx"
class="pictrue" class="pictrue"
> >
<image :src="itemn" class="image" @click="previewImage(item.picturesArr, eq)" /> <image :src="pic" class="image" mode="aspectFill" @click="previewImage(item.picturesArr, idx)" />
</view> </view>
</view> </view>
<!-- 商家回复 -->
<view class="reply" v-if="item.merchantReplyContent"> <view class="reply" v-if="item.merchantReplyContent">
<span class="font-color-red">店小二</span> <text class="font-color-red">店小二</text>
:{{item.merchantReplyContent}} :{{item.merchantReplyContent}}
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
import { dataFormat } from "@/utils"; import { dataFormat } from "@/utils";
@@ -105,30 +126,122 @@ export default {
} }
}; };
</script> </script>
<style lang="less" scoped>
.attr-bg{ <style lang="less" scoped>
background: rgba(197,39,51,0.1); .evaluateWtapper {
padding: 7rpx 5px; .evaluateItem {
width: fit-content; margin-bottom: 30rpx;
border-radius: 12rpx; border-bottom: 1px solid #f5f5f5;
} padding-bottom: 30rpx;
.time{ &:last-child {
padding: 0 !important; border-bottom: none;
} }
.evaluateWtapper .evaluateItem .imgList .pictrue{
position: relative;
width: 300rpx;
height: 300rpx;
.play-icon {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
width: 80rpx;
height: 80rpx;
} }
} }
.user-header {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.avatar-box {
margin-right: 20rpx;
.avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
}
}
.user-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.name {
font-size: 28rpx;
color: #333;
font-weight: bold;
margin-bottom: 4rpx;
}
.time {
font-size: 22rpx;
color: #999;
}
}
}
.tag-row {
margin-bottom: 12rpx;
.default-tag {
display: inline-block;
background: #FFF0F1;
color: #FE5261;
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 6rpx;
}
}
.star-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.star-icon {
margin-right: 4rpx;
}
}
.evaluate-infor {
font-size: 28rpx;
color: #333;
line-height: 1.6;
margin-bottom: 20rpx;
text-align: justify;
}
.imgList {
display: flex;
flex-wrap: wrap;
.pictrue {
width: 220rpx;
height: 220rpx;
margin-right: 15rpx;
margin-bottom: 15rpx;
border-radius: 12rpx;
overflow: hidden;
position: relative;
background: #f5f5f5;
&:nth-child(3n) {
margin-right: 0;
}
.image {
width: 100%;
height: 100%;
}
.play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
}
}
}
.reply {
background: #F5F5F5;
border-radius: 12rpx;
padding: 20rpx;
margin-top: 10rpx;
font-size: 24rpx;
color: #666;
line-height: 1.5;
}
</style> </style>
+66 -16
View File
@@ -35,6 +35,7 @@
<script> <script>
import UserEvaluation from "@/components/UserEvaluation"; import UserEvaluation from "@/components/UserEvaluation";
import { getReplyConfig, getReplyList } from "@/api/store"; import { getReplyConfig, getReplyList } from "@/api/store";
import { getJiaLouProductReply, getJiaLouProductReplyCount } from "@/api/qianxian";
import Loading from "@/components/Loading"; import Loading from "@/components/Loading";
export default { export default {
@@ -47,6 +48,7 @@ export default {
data: function() { data: function() {
return { return {
product_id: 0, product_id: 0,
isTravelGroup: false,
replyData: {}, replyData: {},
navList: [ navList: [
{ evaluate: "全部", num: 0 }, { evaluate: "全部", num: 0 },
@@ -65,6 +67,7 @@ export default {
}, },
mounted: function() { mounted: function() {
this.product_id = this.$yroute.query.id; this.product_id = this.$yroute.query.id;
this.isTravelGroup = String(this.$yroute.query.isTravelGroup || '') === '1';
this.getProductReplyCount(); this.getProductReplyCount();
this.getProductReplyList(); this.getProductReplyList();
}, },
@@ -74,27 +77,74 @@ export default {
methods: { methods: {
getProductReplyCount: function() { getProductReplyCount: function() {
let that = this; let that = this;
getReplyConfig(that.product_id).then(res => { if (that.isTravelGroup) {
that.$set(that, "replyData", res.data); getJiaLouProductReplyCount({
that.navList[0].num = res.data.sumCount; travelGroupProductId: that.product_id
that.navList[1].num = res.data.goodCount; }).then(res => {
that.navList[2].num = res.data.inCount; const data = res.data || {};
that.navList[3].num = res.data.poorCount; that.$set(that, "replyData", data);
}); that.navList[0].num = data.sumCount || 0;
that.navList[1].num = data.goodCount || 0;
that.navList[2].num = data.inCount || 0;
that.navList[3].num = data.poorCount || 0;
});
} else {
getReplyConfig(that.product_id).then(res => {
const data = res.data || {};
that.$set(that, "replyData", data);
that.navList[0].num = data.sumCount || 0;
that.navList[1].num = data.goodCount || 0;
that.navList[2].num = data.inCount || 0;
that.navList[3].num = data.poorCount || 0;
});
}
}, },
getProductReplyList: function() { getProductReplyList: function() {
let that = this; let that = this;
if (that.loading) return; //阻止下次请求(false可以进行请求); if (that.loading) return;
if (that.loadend) return; //阻止结束当前请求(false可以进行请求); if (that.loadend) return;
that.loading = true; that.loading = true;
let q = { page: that.page, limit: that.limit, type: that.currentActive }; let q = { page: that.page, limit: that.limit, type: that.currentActive };
getReplyList(that.product_id, q).then(res => { if (that.isTravelGroup) {
that.loading = false; getJiaLouProductReply({
//apply();js将一个数组插入另一个数组; travelGroupProductId: that.product_id,
that.reply.push.apply(that.reply, res.data); type: q.type,
that.loadend = res.data.length < that.limit; //判断所有数据是否加载完成; page: q.page,
that.page = that.page + 1; limit: q.limit
}); }).then(res => {
that.loading = false;
const pageData = res.data || {};
const rawList = Array.isArray(pageData.records) ? pageData.records : [];
const list = rawList.map(item => {
const productScore = Number(item.productScore) || 0;
const serviceScore = Number(item.serviceScore) || 0;
let star = productScore || serviceScore || 5;
const skuText = '默认';
const picturesArr = Array.isArray(item.pics) ? item.pics : [];
return {
...item,
star,
sku: skuText,
picturesArr
};
});
that.reply.push.apply(that.reply, list);
that.loadend = list.length < that.limit || !pageData.pages || that.page >= pageData.pages;
that.page = that.page + 1;
}).catch(() => {
that.loading = false;
});
} else {
getReplyList(that.product_id, q).then(res => {
that.loading = false;
const data = res.data || [];
that.reply.push.apply(that.reply, data);
that.loadend = data.length < that.limit;
that.page = that.page + 1;
}).catch(() => {
that.loading = false;
});
}
}, },
changeType: function(index) { changeType: function(index) {
let that = this; let that = this;
+135 -39
View File
@@ -155,6 +155,26 @@
<image :src="img" class="v12-radius-40" style="width: 100%;" mode="aspectFill" @click="previewHero(detail.detailImages, img)" /> <image :src="img" class="v12-radius-40" style="width: 100%;" mode="aspectFill" @click="previewHero(detail.detailImages, img)" />
</view> </view>
</view> </view>
<view class="v12-mt-3">
<view class="section v12-white review-section">
<view class="review-header" @click="goEvaluateList">
<text class="v12-pl-2 v12-font-bold v12-font-36 v12-align-center title-wrap">店铺评价</text>
<text class="iconfont icon-jiantou review-arrow"></text>
</view>
<view v-if="reviewList.length">
<UserEvaluation :reply="reviewList" />
<view class="review-load-more" v-if="!reviewLoadEnd" @click="fetchReviewList()">
{{ reviewLoading ? '加载中...' : '查看更多评价' }}
</view>
<view class="review-load-more review-no-more" v-else>
没有更多评价了
</view>
</view>
<view v-else class="review-empty">
暂无评价
</view>
</view>
</view>
<view class="bottom-space"></view> <view class="bottom-space"></view>
</scroll-view> </scroll-view>
@@ -198,13 +218,15 @@
</template> </template>
<script> <script>
import { getJiaLouProductDetail, getJiaLouCalendar,getJiaLouBookingRules } from '@/api/qianxian' import { getJiaLouProductDetail, getJiaLouCalendar,getJiaLouBookingRules, getJiaLouProductReply } from '@/api/qianxian'
import {getGiftCardSendBackground} from "@/api/gift" import {getGiftCardSendBackground} from "@/api/gift"
import DateRangePicker from '@/components/DateRangePicker/DateRangePicker.vue' import DateRangePicker from '@/components/DateRangePicker/DateRangePicker.vue'
import UserEvaluation from '@/components/UserEvaluation'
export default { export default {
name: 'GroupBuyDetails', name: 'GroupBuyDetails',
components: { components: {
DateRangePicker DateRangePicker,
UserEvaluation
}, },
data() { data() {
return { return {
@@ -218,7 +240,13 @@ export default {
calendar: [], calendar: [],
currentWeek: [], currentWeek: [],
rules: "", rules: "",
rangeDate: {} rangeDate: {},
reviewActiveType: 0,
reviewList: [],
reviewPage: 1,
reviewLimit: 5,
reviewLoading: false,
reviewLoadEnd: false
} }
}, },
onShow() { onShow() {
@@ -231,6 +259,7 @@ export default {
onLoad(query) { onLoad(query) {
this.id = (query && query.id) || '' this.id = (query && query.id) || ''
this.getDetail() this.getDetail()
this.fetchReviewList(true)
// this.getCalendar() // this.getCalendar()
getJiaLouBookingRules(this.id).then(res => { getJiaLouBookingRules(this.id).then(res => {
if (res.status === 200) { if (res.status === 200) {
@@ -238,6 +267,11 @@ export default {
} }
}) })
}, },
onReachBottom() {
if (!this.reviewLoadEnd) {
this.fetchReviewList()
}
},
methods: { methods: {
previewHero(urls, current) { previewHero(urls, current) {
console.log(urls); console.log(urls);
@@ -369,6 +403,16 @@ export default {
} }
uni.navigateTo({ url: '/pkg_product/views/submitOrder?id=' + this.id + '&orderStartDate=' + this.dateRange[0] + '&orderEndDate=' + this.dateRange[1] }) uni.navigateTo({ url: '/pkg_product/views/submitOrder?id=' + this.id + '&orderStartDate=' + this.dateRange[0] + '&orderEndDate=' + this.dateRange[1] })
}, },
goEvaluateList() {
if (!this.id) return
this.$yrouter.push({
path: '/pages/shop/EvaluateList/index',
query: {
id: this.id,
isTravelGroup: 1
}
})
},
/** /**
* 查找指定日期的预约状态 * 查找指定日期的预约状态
* @param {string} dateStr YYYY-MM-DD格式的日期字符串 * @param {string} dateStr YYYY-MM-DD格式的日期字符串
@@ -392,47 +436,49 @@ export default {
return '不可订' return '不可订'
} }
}, },
async fetchReviewList(reset = false) {
/** if (this.reviewLoading) return
* 处理日期点击事件 if (!reset && this.reviewLoadEnd) return
* @param {Object} date 日期对象 if (!this.id) return
*/ if (reset) {
handleDateClick(date) { this.reviewPage = 1
// 如果是不可订的日期,直接返回 this.reviewLoadEnd = false
if (date.status === '不可订') { this.reviewList = []
uni.showToast({
title: '该日期不可预订',
icon: 'none'
})
return
} }
this.reviewLoading = true
// 检查是否已选中 try {
const index = this.rangeDate.indexOf(date.date) const res = await getJiaLouProductReply({
if (index > -1) { travelGroupProductId: this.id,
// 如果已选中,则取消选中 type: this.reviewActiveType,
this.rangeDate.splice(index, 1) page: this.reviewPage,
} else { limit: this.reviewLimit
// 如果未选中,则添加到选中列表 })
this.rangeDate.push(date.date) if (res && res.status === 200 && res.data) {
// 对日期进行排序 const pageData = res.data
this.rangeDate.sort() const rawList = Array.isArray(pageData.records) ? pageData.records : []
const list = rawList.map(item => {
// 检查日期连续性 const productScore = Number(item.productScore) || 0
if (this.rangeDate.length > 1) { const serviceScore = Number(item.serviceScore) || 0
const isConsecutive = this.checkConsecutiveDates(this.rangeDate) let star = productScore || serviceScore || 5
if (!isConsecutive) { const skuText = '默认'
uni.showToast({ const picturesArr = Array.isArray(item.pics) ? item.pics : []
title: '请选择连续的日期', return {
icon: 'none' ...item,
}) star,
// 移除最后添加的日期 sku: skuText,
this.rangeDate.pop() picturesArr
}
})
this.reviewList = this.reviewList.concat(list)
this.reviewLoadEnd = list.length === 0 || this.reviewPage >= (pageData.pages || 0)
if (!this.reviewLoadEnd) {
this.reviewPage += 1
} }
} }
} finally {
this.reviewLoading = false
} }
}, },
/** /**
* 检查日期是否被选中 * 检查日期是否被选中
* @param {string} date 日期字符串 * @param {string} date 日期字符串
@@ -562,6 +608,56 @@ export default {
.gift-wrap{ .gift-wrap{
position: relative; position: relative;
} }
.review-section{
padding-top: 20rpx;
padding-bottom: 24rpx;
}
.review-header{
display: flex;
align-items: center;
justify-content: space-between;
}
.review-arrow{
font-size: 28rpx;
color: #333;
}
.review-title{
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.review-tabs{
margin-top: 24rpx;
display: flex;
flex-wrap: nowrap;
}
.review-tab-item{
padding: 8rpx 24rpx;
border-radius: 40rpx;
background: #f5f5f5;
font-size: 24rpx;
color: #666;
margin-right: 16rpx;
}
.review-tab-item.active{
background: #C52733;
color: #fff;
}
.review-load-more{
margin-top: 24rpx;
text-align: center;
font-size: 24rpx;
color: #999;
}
.review-no-more{
color: #ccc;
}
.review-empty{
margin-top: 32rpx;
text-align: center;
font-size: 24rpx;
color: #999;
}
.intro-text{ .intro-text{
font-weight: 400; font-weight: 400;
font-size: 26rpx; font-size: 26rpx;