Browse Source

feat(RecentReviews): 新增近期评价组件并集成到伴宠师详情页

refactor(personalCenter): 优化性格选择组件逻辑与交互
fix(index): 注释掉未使用的getPeopleList调用
master
前端-胡立永 2 months ago
parent
commit
809e7f3e3e
7 changed files with 453 additions and 124 deletions
  1. +12
    -0
      api/order/order.js
  2. +1
    -1
      pages/index.vue
  3. +30
    -25
      pages/personalCenter/index.vue
  4. +89
    -40
      pages/personalCenter/personalitySelect.vue
  5. +28
    -58
      pages_order/companionPetList/companionPetInfo.vue
  6. +117
    -0
      pages_order/components/RecentReviews/README.md
  7. +176
    -0
      pages_order/components/RecentReviews/RecentReviews.vue

+ 12
- 0
api/order/order.js View File

@ -202,4 +202,16 @@ export const getTecByUser = (params) => {
method: 'get',
params
})
}
// 查询指定伴宠师近期评价
export const getTeacherEvaluate = (params) => {
return request({
headers: {
"isToken": true
},
url: "/applet/mall/teacher/getTeacherEvaluate",
method: 'get',
params
})
}

+ 1
- 1
pages/index.vue View File

@ -883,7 +883,7 @@
onLoad: function() {
this.init()
this.getPeopleList()
// this.getPeopleList()
this.getProductList()
this.getBanner()
const accountInfo = wx.getAccountInfoSync();


+ 30
- 25
pages/personalCenter/index.vue View File

@ -341,30 +341,31 @@
// }else{
// this.active=1
// }
const params = {
orderId: orderInfo.orderId,
pageNumber: this.page,
pageSize: 9999999,
status : 2
};
this.active=2
this.orderDateFrequencyId = orderInfo.orderId
// const params = {
// orderId: orderInfo.orderId,
// pageNumber: this.page,
// pageSize: 9999999,
// status : 2
// };
appletOrderDateFrequencyList(params).then(res => {
if (res && res.data) {
this.active=2
this.orderDateFrequencyList = res.data
// appletOrderDateFrequencyList(params).then(res => {
// if (res && res.data) {
// this.active=2
// this.orderDateFrequencyList = res.data
for (let index = 0; index < res.data.length; index++) {
const element = res.data[index];
element.list.forEach(n => {
if (n.status == 2) {
this.orderDateFrequencyId = n.id
}
})
}
}
}).catch(err => {
});
// for (let index = 0; index < res.data.length; index++) {
// const element = res.data[index];
// element.list.forEach(n => {
// if (n.status == 2) {
// this.orderDateFrequencyId = n.id
// }
// })
// }
// </span><span class="err"> pan> }
// }).catch(err => {
// });
}else if(orderInfo.status==3){
this.active = 3
}
@ -410,13 +411,17 @@
// this.$modal.showToast('');
// return;
// }
uni.navigateTo({
url: `/pages_order/order/serviceRecord?orderId=${this.orderDateFrequencyId}`
});
// 2
// if (e.index === 2 && this.active === 2) {
//
uni.navigateTo({
url: `/pages/personalCenter/orderDetailImage?id=${this.orderDateFrequencyId}`
});
// uni.navigateTo({
// url: `/pages/personalCenter/orderDetailImage?id=${this.orderDateFrequencyId}`
// });
// }
},
openOtherPage(type){


+ 89
- 40
pages/personalCenter/personalitySelect.vue View File

@ -55,6 +55,22 @@
originalPersonality: ''
}
},
watch: {
// ,
personalityDescription: {
handler(newVal) {
this.syncSelectedPersonalities();
},
immediate: true
},
//
personalityOptions: {
handler() {
this.syncSelectedPersonalities();
},
immediate: true
}
},
onLoad(options) {
//
if (options.personality) {
@ -66,6 +82,24 @@
this.getPersonalityDataList();
},
methods: {
//
syncSelectedPersonalities() {
if (!this.personalityDescription || this.personalityOptions.length === 0) {
this.selectedPersonalities = [];
return;
}
//
const descriptionItems = this.personalityDescription.split(',')
.map(item => item.trim())
.filter(item => item.length > 0);
//
this.selectedPersonalities = descriptionItems.filter(item =>
this.personalityOptions.includes(item)
);
},
//
parsePersonality(personality) {
if (!personality) return;
@ -85,42 +119,60 @@
this.personalityOptions.includes(item)
);
//
const descriptions = personality.filter(item =>
!this.personalityOptions.includes(item)
);
this.personalityDescription = descriptions.join(',');
//
this.personalityDescription = personality.join(',');
} else {
//
const parts = personality.split(',');
if (parts.length > 1) {
//
const quickSelections = parts[parts.length - 1];
this.selectedPersonalities = this.personalityOptions.filter(item =>
quickSelections.includes(item)
);
//
this.personalityDescription = parts.slice(0, -1).join(',');
} else {
//
this.selectedPersonalities = this.personalityOptions.filter(item =>
personality.includes(item)
);
}
const parts = personality.split(',');
//
this.selectedPersonalities = parts.filter(item =>
this.personalityOptions.includes(item.trim())
);
//
this.personalityDescription = personality;
}
},
//
togglePersonality(personality) {
const index = this.selectedPersonalities.indexOf(personality);
if (index > -1) {
this.selectedPersonalities.splice(index, 1);
const isSelected = this.selectedPersonalities.includes(personality);
if (isSelected) {
//
this.removeFromDescription(personality);
} else {
//
this.addToDescription(personality);
}
// selectedPersonalitieswatch
},
//
addToDescription(personality) {
if (this.personalityDescription.trim()) {
// ,
if (!this.personalityDescription.includes(personality)) {
this.personalityDescription += ',' + personality;
}
} else {
this.selectedPersonalities.push(personality);
// ,
this.personalityDescription = personality;
}
},
//
removeFromDescription(personality) {
if (!this.personalityDescription.includes(personality)) return;
//
let parts = this.personalityDescription.split(',').map(item => item.trim());
//
parts = parts.filter(item => item !== personality);
//
this.personalityDescription = parts.join(',');
},
//
getPersonalityDataList() {
getDictList('pet_personality').then(res => {
@ -139,24 +191,21 @@
savePersonality() {
this.loading = true;
//
let finalPersonality = [];
if (this.personalityDescription.trim()) {
finalPersonality.push(this.personalityDescription.trim());
}
if (this.selectedPersonalities.length > 0) {
finalPersonality = finalPersonality.concat(this.selectedPersonalities);
}
//
if (finalPersonality.length === 0) {
//
if (!this.personalityDescription.trim()) {
this.$modal.showToast('请至少选择一项性格特征');
this.loading = false;
return;
}
// ,
const personalityItems = this.personalityDescription.split(',')
.map(item => item.trim())
.filter(item => item.length > 0);
//
const finalPersonality = Array.from(new Set(personalityItems));
//
setTimeout(() => {
this.loading = false;
@ -166,7 +215,7 @@
//
if (!pages || pages.length < 2) {
console.warn('页面栈不足无法获取上一页')
console.warn('页面栈不足,无法获取上一页')
uni.navigateBack();
return;
}
@ -301,4 +350,4 @@
border-radius: 8px;
}
}
</style>
</style>

+ 28
- 58
pages_order/companionPetList/companionPetInfo.vue View File

@ -141,47 +141,6 @@
</view>
</uni-card> -->
</view>
<!-- <view class="personal-pet" style="padding-bottom: 10px;">
<uni-card :is-shadow="false" padding=0 margin="10px">
<view class="service-new-title" slot="title">
<view class="service-new-title-left">
<view class="service-new-flag"></view>
<view>近期评价</view>
</view>
</view>
<view class="split-line"></view>
<view class="service-new-pet-content">
<view v-for="(item,index) in rewardList" :key="index">
<view class="reward-list-item">
<view class="reward-info">
<view>
<u-avatar :src="item.photo?item.photo:defaultStaffIamge" size="60"
shape="circle"></u-avatar>
</view>
<view class="reward-info-1">
<view class="reward-info-2">
<view class="reward-name">
{{item.name}}
</view>
<view class="star">
<uni-rate :readonly="true" size="18" :value="item.star" />
</view>
</view>
<view class="reward-info-3" style="width: 100%;">
<view>
{{item.time}}
</view>
<view>
{{item.des}}
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</uni-card>
</view> -->
<!-- <view class="service-record" style="padding-bottom: 20rpx;">
<uni-card :is-shadow="false" padding="0" margin="10px">
@ -252,6 +211,12 @@
</view>
<!-- 近期评价组件 -->
<RecentReviews
:reviewList="recentReviews"
:defaultAvatar="defaultStaffIamge"
@reviewClick="onReviewClick"
@avatarClick="onAvatarClick" />
<view class="service-record" style="padding-bottom: 55px;">
<uni-card :is-shadow="false" padding=0 margin="10px">
@ -359,15 +324,18 @@
getTeacherPetList,
getTeacherAddressList,
getTeacherServiceLogList,
getTeacherEvaluate,
} from "@/api/order/order"
import uniRate from '@/uni_modules/uni-rate/components/uni-rate/uni-rate.vue';
import positionMixin from '../../mixins/position';
import addressMap from '@/components/addressMap.vue'
import RecentReviews from '../components/RecentReviews/RecentReviews.vue'
import { mapState } from 'vuex'
export default {
mixins: [positionMixin],
components : {
addressMap
addressMap,
RecentReviews
},
data() {
return {
@ -394,22 +362,8 @@
// }
],
addressList: [],
rewardList: [{
name: '小咪',
star: 3,
time: '2025-1-1 18:00',
des: '服务贴心,态度热情,非常满意',
}, {
name: '中咪',
star: 4,
time: '2025-1-1 18:00',
des: '服务贴心,态度热情,非常满意',
}, {
name: '大咪',
star: 5,
time: '2025-1-1 18:00',
des: '服务贴心,态度热情,非常满意',
}],
//
recentReviews: [],
serviceRecordList: [
// {
// name: '',
@ -488,8 +442,15 @@
},
mounted() {
this.getCurrentCompanionPetInfo(this.currentCompanionPetId)
this.getTeacherEvaluate()
},
methods: {
getTeacherEvaluate(){
getTeacherEvaluate({id: this.currentCompanionPetId})
.then(res => {
this.recentReviews = res.data.records
})
},
clickAddress(address){
if(address.appletOutDate && address.appletOutDate.length > 0){
this.selectDate = address.appletOutDate.map(n => n.date);
@ -546,6 +507,15 @@
urls
})
},
//
onReviewClick(data) {
console.log('点击评价:', data);
//
},
onAvatarClick(data) {
console.log('点击用户头像:', data);
//
},
}
}
</script>


+ 117
- 0
pages_order/components/RecentReviews/README.md View File

@ -0,0 +1,117 @@
# RecentReviews 近期评价组件
## 组件说明
近期评价组件用于显示用户对服务的评价信息,包含用户头像、姓名、评价时间、星级评分和评价内容。
## 功能特性
- 📱 符合微信小程序开发规范
- 🎨 与项目整体UI风格保持一致
- 🔄 支持uniapp跨平台开发
- 📊 支持星级评分显示
- 🖼️ 支持用户头像展示
- 📝 支持评价内容展示
- 🎯 支持点击事件处理
- 📱 响应式布局设计
## 使用方法
### 1. 引入组件
```javascript
import RecentReviews from '@/components/RecentReviews/RecentReviews.vue'
export default {
components: {
RecentReviews
}
}
```
### 2. 在模板中使用
```vue
<template>
<view>
<RecentReviews
:reviewList="reviewData"
:defaultAvatar="defaultAvatar"
@reviewClick="handleReviewClick"
@avatarClick="handleAvatarClick" />
</view>
</template>
```
### 3. 数据格式
```javascript
data() {
return {
reviewData: [
{
userName: '用户名',
userImage: '用户头像URL',
rating: 5, // 评分 1-5
reviewDate: '2024-11-01 18:09:32',
comment: '评价内容'
}
],
defaultAvatar: 'https://example.com/default-avatar.png'
}
}
```
## Props 属性
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| reviewList | Array | [] | 评价数据列表 |
| defaultAvatar | String | 默认头像URL | 默认用户头像 |
| showCount | Boolean | true | 是否显示评价数量 |
## Events 事件
| 事件名 | 说明 | 回调参数 |
|--------|------|----------|
| reviewClick | 点击评价项时触发 | { review, index } |
| avatarClick | 点击用户头像时触发 | { review, index } |
## 数据结构说明
### reviewList 数组项结构
```typescript
interface ReviewItem {
userName: string; // 用户名
userImage?: string; // 用户头像URL(可选)
rating: number; // 评分(1-5)
reviewDate: string; // 评价时间
comment: string; // 评价内容
}
```
## 样式说明
组件使用了项目统一的设计规范:
- 主色调:#FFB13F(橙色)
- 文字颜色:#333333(深灰)、#7D8196(浅灰)
- 分割线:#EFEFEF
- 圆角:使用rpx单位,适配不同屏幕
## 注意事项
1. 确保项目中已安装 `uni-rate``u-avatar` 组件
2. 评分值应在 1-5 之间
3. 头像URL建议使用HTTPS协议
4. 组件内部已处理空状态显示
5. 支持微信小程序、H5、APP等多端运行
## 更新日志
### v1.0.0
- 初始版本发布
- 支持基础评价展示功能
- 支持点击事件处理
- 支持空状态显示

+ 176
- 0
pages_order/components/RecentReviews/RecentReviews.vue View File

@ -0,0 +1,176 @@
<template>
<view class="recent-reviews">
<uni-card :is-shadow="false" padding="0" margin="10px">
<view class="service-new-title" slot="title">
<view class="service-new-title-left">
<view class="service-new-flag"></view>
<view>近期评价({{reviewCount}})</view>
</view>
</view>
<view class="split-line"></view>
<view class="recent-reviews-content">
<view v-for="(item, index) in reviewList" :key="index" class="review-item">
<view class="review-header">
<view class="reviewer-info">
<u-avatar :src="item.member.avatar || defaultAvatar" size="40" shape="circle"></u-avatar>
<view class="reviewer-details">
<view class="reviewer-name">{{item.member.nickname}}</view>
<view class="review-date">{{item.createTime}}</view>
</view>
</view>
<view class="rating-stars">
<uni-rate :readonly="true" size="18" :value="item.num" />
</view>
</view>
<view class="review-content">
{{item.content}}
</view>
</view>
<!-- 空状态 -->
<view v-if="!reviewList || reviewList.length === 0" class="empty-state">
<view class="empty-text">暂无评价</view>
</view>
</view>
</uni-card>
</view>
</template>
<script>
export default {
name: 'RecentReviews',
props: {
//
reviewList: {
type: Array,
default: () => []
},
//
defaultAvatar: {
type: String,
default: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/banner/gold_people.png'
},
//
showCount: {
type: Boolean,
default: true
}
},
computed: {
//
reviewCount() {
return this.reviewList ? this.reviewList.length : 0;
}
},
methods: {
//
onReviewClick(review, index) {
this.$emit('reviewClick', { review, index });
},
//
onAvatarClick(review, index) {
this.$emit('avatarClick', { review, index });
}
}
};
</script>
<style lang="scss" scoped>
.recent-reviews {
width: 100%;
padding-bottom: 10px;
.service-new-title {
display: flex;
font-weight: 500;
font-size: 28rpx;
color: #333333;
line-height: 33rpx;
margin: 42rpx 0 30rpx;
justify-content: space-between;
.service-new-title-left {
display: flex;
align-items: center;
.service-new-flag {
width: 8rpx;
height: 32rpx;
background: #FFBF60;
border-radius: 30rpx;
margin-right: 10rpx;
}
}
}
.split-line {
width: 100%;
height: 1rpx;
background: #EFEFEF;
}
.recent-reviews-content {
padding: 20rpx;
.review-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #EFEFEF;
&:last-child {
border-bottom: none;
}
.review-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.reviewer-info {
display: flex;
align-items: center;
.reviewer-details {
margin-left: 20rpx;
.reviewer-name {
font-size: 28rpx;
font-weight: 500;
color: #333333;
line-height: 32rpx;
}
.review-date {
font-size: 24rpx;
color: #7D8196;
margin-top: 8rpx;
line-height: 28rpx;
}
}
}
.rating-stars {
display: flex;
align-items: center;
}
}
.review-content {
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
margin-left: 80rpx;
}
}
.empty-state {
padding: 60rpx 0;
text-align: center;
.empty-text {
font-size: 28rpx;
color: #7D8196;
}
}
}
}
</style>

Loading…
Cancel
Save