You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
485 lines
11 KiB
485 lines
11 KiB
<!--服务列表内容组件-->
|
|
<template>
|
|
<view class="service-list-card">
|
|
<!-- 服务列表 -->
|
|
<view class="service-list" v-if="serviceList && serviceList.length > 0">
|
|
<view
|
|
class="service-item"
|
|
v-for="(service, index) in serviceList"
|
|
:key="service.id || index"
|
|
@click="viewServiceDetail(service)"
|
|
>
|
|
<!-- 服务头部 -->
|
|
<view class="service-header">
|
|
<view class="service-image" v-if="service.preview_image_url">
|
|
<image
|
|
:src="service.preview_image_url"
|
|
mode="aspectFill"
|
|
class="service-img"
|
|
></image>
|
|
</view>
|
|
<view class="service-image placeholder" v-else>
|
|
<text class="placeholder-text">🛠️</text>
|
|
</view>
|
|
|
|
<view class="service-info">
|
|
<view class="service-name">{{ service.service_name || '未知服务' }}</view>
|
|
<view :class="['service-status',getStatusClass(service.status)]">
|
|
{{ getStatusText(service.status) }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 服务描述 -->
|
|
<view class="service-description" v-if="service.description">
|
|
{{ service.description }}
|
|
</view>
|
|
|
|
<!-- 服务记录详情 -->
|
|
<view class="service-logs" v-if="service.logs && service.logs.length > 0">
|
|
<view class="logs-title">服务记录</view>
|
|
<view class="log-list">
|
|
<view
|
|
class="log-item"
|
|
v-for="(log, logIndex) in service.logs"
|
|
:key="log.id || logIndex"
|
|
>
|
|
<view class="log-header">
|
|
<view class="log-time" v-if="log.service_time">
|
|
{{ formatTime(log.service_time) }}
|
|
</view>
|
|
<view :class="['log-status',getLogStatusClass(log.status)]">
|
|
{{ getLogStatusText(log.status) }}
|
|
</view>
|
|
</view>
|
|
|
|
<view class="log-details">
|
|
<view class="log-detail-item" v-if="log.service_content">
|
|
<text class="detail-label">服务内容:</text>
|
|
<text class="detail-value">{{ log.service_content }}</text>
|
|
</view>
|
|
|
|
<view class="log-detail-item" v-if="log.service_staff">
|
|
<text class="detail-label">服务人员:</text>
|
|
<text class="detail-value">{{ log.service_staff }}</text>
|
|
</view>
|
|
|
|
<view class="log-detail-item" v-if="log.duration">
|
|
<text class="detail-label">服务时长:</text>
|
|
<text class="detail-value">{{ log.duration }}分钟</text>
|
|
</view>
|
|
|
|
<view class="log-detail-item" v-if="log.service_location">
|
|
<text class="detail-label">服务地点:</text>
|
|
<text class="detail-value">{{ log.service_location }}</text>
|
|
</view>
|
|
|
|
<view class="log-detail-item" v-if="log.customer_feedback">
|
|
<text class="detail-label">客户反馈:</text>
|
|
<text class="detail-value feedback">{{ log.customer_feedback }}</text>
|
|
</view>
|
|
|
|
<view class="log-detail-item" v-if="log.service_rating">
|
|
<text class="detail-label">服务评分:</text>
|
|
<view class="rating-stars">
|
|
<text
|
|
class="star"
|
|
v-for="n in 5"
|
|
:key="n"
|
|
:class="{ 'active': n <= log.service_rating }"
|
|
>★</text>
|
|
<text class="rating-text">({{ log.service_rating }}/5)</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="log-detail-item" v-if="log.remark">
|
|
<text class="detail-label">备注:</text>
|
|
<text class="detail-value remark">{{ log.remark }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 服务统计信息 -->
|
|
<view class="service-stats" v-if="service.total_count || service.completed_count">
|
|
<view class="stat-item">
|
|
<text class="stat-label">总次数:</text>
|
|
<text class="stat-value">{{ service.total_count || 0 }}次</text>
|
|
</view>
|
|
<view class="stat-item">
|
|
<text class="stat-label">已完成:</text>
|
|
<text class="stat-value">{{ service.completed_count || 0 }}次</text>
|
|
</view>
|
|
<view class="stat-item" v-if="service.total_count">
|
|
<text class="stat-label">剩余:</text>
|
|
<text class="stat-value highlight">{{ (service.total_count - (service.completed_count || 0)) }}次</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 空状态 -->
|
|
<view class="empty-state" v-else>
|
|
<view class="empty-icon">🔧</view>
|
|
<view class="empty-text">暂无服务记录</view>
|
|
<view class="empty-tip">客户还未使用任何服务</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'ServiceListCard',
|
|
props: {
|
|
// 服务列表数据
|
|
serviceList: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
// 查看服务详情
|
|
viewServiceDetail(service) {
|
|
this.$emit('view-detail', service)
|
|
},
|
|
|
|
// 获取服务状态样式类
|
|
getStatusClass(status) {
|
|
const statusMap = {
|
|
'active': 'status-active',
|
|
'completed': 'status-completed',
|
|
'suspended': 'status-suspended',
|
|
'expired': 'status-expired'
|
|
}
|
|
return statusMap[status] || 'status-default'
|
|
},
|
|
|
|
// 获取服务状态文本
|
|
getStatusText(status) {
|
|
const statusMap = {
|
|
'active': '进行中',
|
|
'completed': '已完成',
|
|
'suspended': '已暂停',
|
|
'expired': '已过期'
|
|
}
|
|
return statusMap[status] || '未知状态'
|
|
},
|
|
|
|
// 获取日志状态样式类
|
|
getLogStatusClass(status) {
|
|
const statusMap = {
|
|
'completed': 'log-completed',
|
|
'cancelled': 'log-cancelled',
|
|
'pending': 'log-pending'
|
|
}
|
|
return statusMap[status] || 'log-default'
|
|
},
|
|
|
|
// 获取日志状态文本
|
|
getLogStatusText(status) {
|
|
const statusMap = {
|
|
'completed': '已完成',
|
|
'cancelled': '已取消',
|
|
'pending': '待处理'
|
|
}
|
|
return statusMap[status] || '未知'
|
|
},
|
|
|
|
// 格式化时间
|
|
formatTime(timeStr) {
|
|
if (!timeStr) return ''
|
|
try {
|
|
const date = new Date(timeStr)
|
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
|
} catch (e) {
|
|
return timeStr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.service-list-card {
|
|
padding: 0;
|
|
}
|
|
|
|
.service-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 32rpx;
|
|
}
|
|
|
|
.service-item {
|
|
background: #3A3A3A;
|
|
border-radius: 16rpx;
|
|
padding: 32rpx;
|
|
border: 1px solid #404040;
|
|
transition: all 0.3s ease;
|
|
|
|
&:active {
|
|
background: #4A4A4A;
|
|
}
|
|
}
|
|
|
|
.service-header {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.service-image {
|
|
width: 80rpx;
|
|
height: 80rpx;
|
|
border-radius: 16rpx;
|
|
margin-right: 24rpx;
|
|
overflow: hidden;
|
|
|
|
&.placeholder {
|
|
background: rgba(41, 211, 180, 0.2);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
|
|
.service-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.placeholder-text {
|
|
font-size: 40rpx;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.service-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.service-name {
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
color: #ffffff;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
|
|
.service-status {
|
|
padding: 6rpx 12rpx;
|
|
border-radius: 16rpx;
|
|
font-size: 22rpx;
|
|
font-weight: 500;
|
|
display: inline-block;
|
|
|
|
&.status-active {
|
|
background: rgba(41, 211, 180, 0.2);
|
|
color: #29D3B4;
|
|
}
|
|
|
|
&.status-completed {
|
|
background: rgba(76, 175, 80, 0.2);
|
|
color: #4CAF50;
|
|
}
|
|
|
|
&.status-suspended {
|
|
background: rgba(255, 193, 7, 0.2);
|
|
color: #FFC107;
|
|
}
|
|
|
|
&.status-expired {
|
|
background: rgba(244, 67, 54, 0.2);
|
|
color: #F44336;
|
|
}
|
|
|
|
&.status-default {
|
|
background: rgba(158, 158, 158, 0.2);
|
|
color: #9E9E9E;
|
|
}
|
|
}
|
|
|
|
.service-description {
|
|
font-size: 26rpx;
|
|
color: #cccccc;
|
|
line-height: 1.5;
|
|
margin-bottom: 24rpx;
|
|
padding: 20rpx;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 12rpx;
|
|
}
|
|
|
|
.service-logs {
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.logs-title {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #ffffff;
|
|
margin-bottom: 20rpx;
|
|
padding-bottom: 12rpx;
|
|
border-bottom: 1px solid #404040;
|
|
}
|
|
|
|
.log-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.log-item {
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border-radius: 12rpx;
|
|
padding: 24rpx;
|
|
border-left: 3rpx solid #29D3B4;
|
|
}
|
|
|
|
.log-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
|
|
.log-time {
|
|
font-size: 24rpx;
|
|
color: #999999;
|
|
}
|
|
|
|
.log-status {
|
|
padding: 4rpx 10rpx;
|
|
border-radius: 12rpx;
|
|
font-size: 20rpx;
|
|
font-weight: 500;
|
|
|
|
&.log-completed {
|
|
background: rgba(76, 175, 80, 0.2);
|
|
color: #4CAF50;
|
|
}
|
|
|
|
&.log-cancelled {
|
|
background: rgba(244, 67, 54, 0.2);
|
|
color: #F44336;
|
|
}
|
|
|
|
&.log-pending {
|
|
background: rgba(255, 193, 7, 0.2);
|
|
color: #FFC107;
|
|
}
|
|
|
|
&.log-default {
|
|
background: rgba(158, 158, 158, 0.2);
|
|
color: #9E9E9E;
|
|
}
|
|
}
|
|
|
|
.log-details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12rpx;
|
|
}
|
|
|
|
.log-detail-item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.detail-label {
|
|
font-size: 24rpx;
|
|
color: #999999;
|
|
min-width: 120rpx;
|
|
margin-right: 16rpx;
|
|
}
|
|
|
|
.detail-value {
|
|
font-size: 24rpx;
|
|
color: #ffffff;
|
|
flex: 1;
|
|
|
|
&.feedback,
|
|
&.remark {
|
|
line-height: 1.5;
|
|
}
|
|
}
|
|
|
|
.rating-stars {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4rpx;
|
|
}
|
|
|
|
.star {
|
|
font-size: 28rpx;
|
|
color: #404040;
|
|
|
|
&.active {
|
|
color: #FFC107;
|
|
}
|
|
}
|
|
|
|
.rating-text {
|
|
font-size: 24rpx;
|
|
color: #999999;
|
|
margin-left: 12rpx;
|
|
}
|
|
|
|
.service-stats {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 32rpx;
|
|
padding: 20rpx;
|
|
background: rgba(41, 211, 180, 0.05);
|
|
border-radius: 12rpx;
|
|
border-left: 4rpx solid #29D3B4;
|
|
}
|
|
|
|
.stat-item {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 24rpx;
|
|
color: #999999;
|
|
margin-right: 8rpx;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 24rpx;
|
|
color: #ffffff;
|
|
font-weight: 600;
|
|
|
|
&.highlight {
|
|
color: #29D3B4;
|
|
}
|
|
}
|
|
|
|
.empty-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 80rpx 40rpx;
|
|
text-align: center;
|
|
}
|
|
|
|
.empty-icon {
|
|
font-size: 120rpx;
|
|
margin-bottom: 32rpx;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.empty-text {
|
|
font-size: 32rpx;
|
|
color: #ffffff;
|
|
margin-bottom: 16rpx;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.empty-tip {
|
|
font-size: 26rpx;
|
|
color: #999999;
|
|
line-height: 1.4;
|
|
}
|
|
</style>
|