202 lines
6.4 KiB
Plaintext
202 lines
6.4 KiB
Plaintext
<template>
|
||
<view class="ratings-page">
|
||
<!-- 顶部总览卡片 -->
|
||
<view class="summary-card">
|
||
<!-- 返回按钮 -->
|
||
<view class="back-box" @click="backToIndex">
|
||
<text class="back-icon">‹</text>
|
||
</view>
|
||
|
||
<view class="summary-left">
|
||
<text class="score">4.9</text>
|
||
<text class="stars">
|
||
<text class="star-on">★</text>
|
||
<text class="star-on">★</text>
|
||
<text class="star-on">★</text>
|
||
<text class="star-on">★</text>
|
||
<text class="star-on">★</text>
|
||
</text>
|
||
<text class="count">共收到 128 条评价</text>
|
||
</view>
|
||
|
||
<view class="summary-right">
|
||
<view class="rate-item">
|
||
<text class="rate-label">好评率</text>
|
||
<text class="rate-value">96.1%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 标签筛选 -->
|
||
<view class="filter-bar">
|
||
<view
|
||
v-for="t in tabs"
|
||
:key="t.key"
|
||
:class="['filter-tab', currentTab===t.key?'active':'']"
|
||
@click="switchTab(t.key)"
|
||
>
|
||
<text>{{ t.label }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 评价列表 -->
|
||
<scroll-view
|
||
scroll-y
|
||
class="ratings-scroll"
|
||
refresher-enabled
|
||
:refresher-triggered="isRefreshing"
|
||
@scrolltolower="loadMore"
|
||
@refresherrefresh="onRefresh"
|
||
>
|
||
<view v-if="list.length" class="rating-list">
|
||
<view v-for="item in list" :key="item.id" class="rating-card">
|
||
<view class="rating-header">
|
||
<image :src="item.avatar" class="user-avatar" mode="aspectFill" />
|
||
<view class="user-info">
|
||
<text class="user-name">{{ item.userName }}</text>
|
||
<text class="rating-stars">
|
||
<text v-for="s in 5" :key="s" :class="s<=item.score?'star-on':'star-off'">★</text>
|
||
</text>
|
||
</view>
|
||
<text class="rating-time">{{ item.time }}</text>
|
||
</view>
|
||
|
||
<text v-if="item.comment" class="rating-comment">{{ item.comment }}</text>
|
||
<text v-else class="rating-comment empty">用户未写评价</text>
|
||
|
||
<view v-if="item.tags&&item.tags.length" class="rating-tags">
|
||
<view v-for="tag in item.tags" :key="tag" class="tag">{{ tag }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="hasMore" class="load-tip">正在加载…</view>
|
||
<view v-else class="load-tip">已加载全部</view>
|
||
</view>
|
||
|
||
<view v-else class="no-data">
|
||
<text class="no-data-icon">📝</text>
|
||
<text class="no-data-text">暂无评价记录</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted } from 'vue'
|
||
|
||
/* 返回按钮 */
|
||
function backToIndex() {
|
||
uni.navigateBack({ url: '/pages/mall/delivery/index' })
|
||
}
|
||
|
||
/* mock 数据 */
|
||
const currentTab = ref('all')
|
||
const isRefreshing = ref(false)
|
||
const hasMore = ref(true)
|
||
const page = ref(1)
|
||
const list = ref<any[]>([])
|
||
|
||
const tabs = [
|
||
{ key: 'all', label: '全部' },
|
||
{ key: 'good', label: '好评' },
|
||
{ key: 'bad', label: '差评' }
|
||
]
|
||
|
||
function mockList() {
|
||
const tagPool = ['准时送达', '着装整洁', '服务热情', '配送快', '餐品完好']
|
||
return Array.from({ length: 10 }, (_, i) => ({
|
||
id: `${currentTab.value}_${page.value}_${i}`,
|
||
userName: '用户' + (Math.random() * 1000).toFixed(0),
|
||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||
score: currentTab.value === 'bad' ? Math.floor(Math.random() * 2) + 1 : Math.floor(Math.random() * 2) + 4,
|
||
comment: Math.random() > 0.3 ? '配送很及时,服务态度很好!' : '',
|
||
tags: tagPool.slice(0, Math.floor(Math.random() * 3) + 1),
|
||
time: `${Math.floor(Math.random() * 60)}分钟前`
|
||
}))
|
||
}
|
||
|
||
async function fetchList(reset = false) {
|
||
if (reset) page.value = 1
|
||
const newList = mockList()
|
||
if (reset) list.value = newList
|
||
else list.value.push(...newList)
|
||
hasMore.value = page.value < 4
|
||
page.value++
|
||
}
|
||
|
||
function switchTab(key: string) {
|
||
if (currentTab.value === key) return
|
||
currentTab.value = key
|
||
fetchList(true)
|
||
}
|
||
|
||
function loadMore() {
|
||
if (!hasMore.value) return
|
||
fetchList(false)
|
||
}
|
||
|
||
function onRefresh() {
|
||
isRefreshing.value = true
|
||
fetchList(true).finally(() => isRefreshing.value = false)
|
||
}
|
||
|
||
onMounted(() => fetchList(true))
|
||
</script>
|
||
|
||
<style scoped>
|
||
.ratings-page{min-height:100vh;background:#f5f5f5;display:flex;flex-direction:column;}
|
||
|
||
.summary-card{
|
||
margin:20rpx 30rpx;
|
||
background:#fff;border-radius:20rpx;padding:40rpx;
|
||
display:flex;align-items:center;position:relative;
|
||
}
|
||
.back-box{
|
||
position:absolute;left:30rpx;top:50%;transform:translateY(-50%);
|
||
width:60rpx;height:60rpx;border-radius:50%;
|
||
background:rgba(0,0,0,.05);display:flex;align-items:center;justify-content:center;
|
||
}
|
||
.back-box:active{background:rgba(0,0,0,.15);}
|
||
.back-icon{font-size:40rpx;color:#333;}
|
||
.summary-left{flex:1;display:flex;flex-direction:center;align-items:center;}
|
||
.score{font-size:64rpx;font-weight:bold;color:#ff9500;margin-right:20rpx;}
|
||
.stars .star-on{color:#ff9500;}
|
||
.count{font-size:24rpx;color:#666;margin-left:20rpx;}
|
||
.summary-right .rate-item{text-align:center;}
|
||
.rate-label{font-size:24rpx;color:#666;}
|
||
.rate-value{font-size:40rpx;font-weight:bold;color:#4caf50;}
|
||
|
||
.filter-bar{
|
||
margin:0 30rpx 20rpx;
|
||
background:#fff;border-radius:20rpx;padding:20rpx;
|
||
display:flex;justify-content:space-around;
|
||
}
|
||
.filter-tab{
|
||
padding:10rpx 30rpx;border-radius:30rpx;font-size:26rpx;
|
||
background:#f0f0f0;color:#666;
|
||
}
|
||
.filter-tab.active{background:#74b9ff;color:#fff;}
|
||
|
||
.ratings-scroll{flex:1;}
|
||
.rating-list{padding:0 30rpx 30rpx;}
|
||
.rating-card{
|
||
background:#fff;border-radius:16rpx;padding:30rpx;margin-bottom:20rpx;
|
||
}
|
||
.rating-header{display:flex;align-items:center;}
|
||
.user-avatar{width:80rpx;height:80rpx;border-radius:50%;margin-right:20rpx;}
|
||
.user-info{flex:1;}
|
||
.user-name{font-size:28rpx;color:#333;}
|
||
.rating-stars{font-size:24rpx;}
|
||
.rating-stars .star-on{color:#ff9500;}
|
||
.rating-stars .star-off{color:#ddd;}
|
||
.rating-time{font-size:22rpx;color:#999;}
|
||
.rating-comment{margin:20rpx 0;font-size:28rpx;color:#333;line-height:1.5;}
|
||
.rating-comment.empty{color:#bbb;}
|
||
.rating-tags{display:flex;flex-wrap:wrap;gap:10rpx;margin-top:15rpx;}
|
||
.tag{background:#e8f4fd;color:#1976d2;font-size:22rpx;padding:4rpx 12rpx;border-radius:8rpx;}
|
||
|
||
.load-tip{text-align:center;font-size:24rpx;color:#999;padding:20rpx 0;}
|
||
.no-data{text-align:center;padding:120rpx 0;}
|
||
.no-data-icon{font-size:80rpx;}
|
||
.no-data-text{font-size:28rpx;color:#999;margin-top:20rpx;}
|
||
</style> |