You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

564 lines
12 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- components/QueueTicketDetail/QueueTicketDetail.vue -->
<template>
<!-- 弹窗遮罩 -->
<view v-if="show" class="ticket-modal-mask" @click="handleClose">
<view class="ticket-modal-content" @click.stop>
<!-- 弹窗头部 -->
<view class="ticket-modal-header">
<view class="header-main">
<text class="modal-title">{{ title }}</text>
<text class="modal-subtitle">排队取号详情信息</text>
</view>
<text class="close-btn" @click="handleClose">×</text>
</view>
<!-- 弹窗内容 -->
<scroll-view class="ticket-modal-body" scroll-y :show-scrollbar="true">
<!-- 票号基本信息卡片 -->
<view class="info-card">
<view class="card-header">
<text class="card-icon">🎫</text>
<text class="card-title">票号信息</text>
</view>
<view class="card-content">
<view class="info-row">
<text class="info-label">票号</text>
<text class="info-value highlight">{{ ticketData.ticketNumber }}</text>
</view>
<view class="info-row">
<text class="info-label">状态</text>
<view class="status-badge" :class="getStatusClass(ticketData.status)">
{{ getStatusText(ticketData.status) }}
</view>
</view>
</view>
</view>
<!-- 时间信息卡片 -->
<view class="info-card">
<view class="card-header">
<text class="card-icon">⏰</text>
<text class="card-title">时间信息</text>
</view>
<view class="card-content compact">
<view class="time-grid">
<view class="time-item">
<text class="time-label">取号日期</text>
<text class="time-value">{{ ticketData.queueDate }}</text>
</view>
<view class="time-item">
<text class="time-label">取号时间</text>
<text class="time-value">{{ ticketData.queueTime }}</text>
</view>
<view class="time-item">
<text class="time-label">等候时长</text>
<text class="time-value duration">{{ ticketData.waitingDuration }}</text>
</view>
<view class="time-item">
<text class="time-label">办理时长</text>
<text class="time-value duration">{{ ticketData.processingDuration }}</text>
</view>
</view>
</view>
</view>
<!-- 办理信息卡片 -->
<view class="info-card">
<view class="card-header">
<text class="card-icon">🏢</text>
<text class="card-title">办理信息</text>
</view>
<view class="card-content compact">
<view class="info-row">
<text class="info-label">办理窗口</text>
<text class="info-value accent">{{ ticketData.serviceWindow }}</text>
</view>
<view class="info-row">
<text class="info-label">办理业务</text>
<text class="info-value">{{ ticketData.businessType }}</text>
</view>
<view class="info-row">
<text class="info-label">工作人员</text>
<text class="info-value primary">{{ ticketData.staffName }}</text>
</view>
</view>
</view>
<!-- 评价信息卡片 -->
<view v-if="ticketData.rating" class="info-card">
<view class="card-header">
<text class="card-icon">⭐</text>
<text class="card-title">服务评价</text>
</view>
<view class="card-content">
<!-- 星级评价 -->
<view class="rating-display">
<view class="stars-container">
<text class="star" v-for="n in 5" :key="n"
:class="{ active: n <= getStarCount(ticketData.rating) }">★</text>
</view>
<text class="rating-text">{{ getRatingText(ticketData.rating) }}</text>
</view>
<!-- 评价详情 -->
<view class="rating-details">
<view class="info-row">
<text class="info-label">评价人</text>
<text class="info-value">{{ ticketData.ratingPerson }}</text>
</view>
<view class="info-row">
<text class="info-label">联系方式</text>
<text class="info-value contact">{{ ticketData.ratingContact }}</text>
</view>
</view>
</view>
</view>
<!-- 无评价提示 -->
<view v-else class="empty-card">
<text class="empty-icon">💬</text>
<text class="empty-text">暂未收到评价</text>
<text class="empty-desc">服务完成后可进行评价</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type {
QueueTicketDetailProps,
QueueTicketDetailEmits,
TicketDetail
} from './QueueTicketDetail.type';
import {
defaultTicketData,
TicketStatus,
RatingResult
} from './QueueTicketDetail.type';
// Props
const props = withDefaults(defineProps<QueueTicketDetailProps>(), {
title: '票号详情',
ticketData: () => defaultTicketData
});
// 组件Emits
const emit = defineEmits<QueueTicketDetailEmits>();
// 计算属性
const ticketData = computed(() => props.ticketData);
// 状态文本映射
const getStatusText = (status: TicketStatus): string => {
const statusMap = {
[TicketStatus.WAITING]: '等待中',
[TicketStatus.PROCESSING]: '办理中',
[TicketStatus.COMPLETED]: '已完成',
[TicketStatus.CANCELLED]: '已取消'
};
return statusMap[status] || '未知状态';
};
// 状态样式类
const getStatusClass = (status: TicketStatus): string => {
const classMap = {
[TicketStatus.WAITING]: 'status-waiting',
[TicketStatus.PROCESSING]: 'status-processing',
[TicketStatus.COMPLETED]: 'status-completed',
[TicketStatus.CANCELLED]: 'status-cancelled'
};
return classMap[status] || '';
};
// 评价文本映射
const getRatingText = (rating: RatingResult): string => {
const ratingMap = {
[RatingResult.EXCELLENT]: '非常满意',
[RatingResult.GOOD]: '满意',
[RatingResult.AVERAGE]: '一般',
[RatingResult.POOR]: '不满意'
};
return ratingMap[rating] || '未评价';
};
// 获取星星数量
const getStarCount = (rating: RatingResult): number => {
const starMap = {
[RatingResult.EXCELLENT]: 5,
[RatingResult.GOOD]: 4,
[RatingResult.AVERAGE]: 3,
[RatingResult.POOR]: 2
};
return starMap[rating] || 0;
};
// 事件处理
const handleClose = () => {
emit('update:show', false);
emit('close');
};
</script>
<style scoped>
.ticket-modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
padding: 40rpx;
}
.ticket-modal-content {
background: white;
border-radius: 20rpx;
width: 100%;
max-height: 75vh;
display: flex;
flex-direction: column;
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.25);
overflow: hidden;
}
/* 头部样式 */
.ticket-modal-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 32rpx 32rpx 24rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: sticky;
top: 0;
z-index: 10;
}
.header-main {
flex: 1;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: white;
display: block;
margin-bottom: 8rpx;
line-height: 1.2;
}
.modal-subtitle {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.9);
display: block;
line-height: 1.2;
}
.close-btn {
font-size: 40rpx;
color: white;
padding: 8rpx 16rpx;
font-weight: 300;
line-height: 1;
}
/* 主体内容 */
.ticket-modal-body {
flex: 1;
padding: 0 24rpx 24rpx;
max-height: calc(75vh - 120rpx);
}
/* 信息卡片 */
.info-card {
background: white;
border-radius: 16rpx;
margin: 24rpx 0 0;
border: 1rpx solid #e8e8e8;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
overflow: hidden;
}
.info-card:first-child {
margin-top: 24rpx;
}
.card-header {
display: flex;
align-items: center;
padding: 24rpx 28rpx;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-bottom: 1rpx solid #f1f5f9;
}
.card-icon {
font-size: 28rpx;
margin-right: 16rpx;
}
.card-title {
font-size: 30rpx;
font-weight: 600;
color: #1e293b;
}
.card-content {
padding: 28rpx;
}
.card-content.compact {
padding: 20rpx 28rpx;
}
/* 信息行 */
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
min-height: 40rpx;
}
.info-row:not(:last-child) {
border-bottom: 1rpx solid #f8fafc;
}
.info-label {
font-size: 28rpx;
color: #64748b;
font-weight: 500;
min-width: 180rpx;
}
.info-value {
font-size: 28rpx;
color: #1e293b;
font-weight: 600;
text-align: right;
flex: 1;
margin-left: 20rpx;
line-height: 1.4;
}
/* 特殊样式 */
.info-value.highlight {
color: #667eea;
font-size: 32rpx;
font-weight: bold;
}
.info-value.accent {
color: #f59e0b;
font-weight: bold;
}
.info-value.primary {
color: #10b981;
font-weight: bold;
}
.info-value.contact {
color: #667eea;
}
.info-value.duration {
color: #ef4444;
font-weight: bold;
}
/* 状态标签 */
.status-badge {
padding: 8rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
min-width: 120rpx;
text-align: center;
}
.status-waiting {
background: #fffbeb;
color: #d97706;
border: 1rpx solid #fcd34d;
}
.status-processing {
background: #eff6ff;
color: #1d4ed8;
border: 1rpx solid #93c5fd;
}
.status-completed {
background: #f0fdf4;
color: #166534;
border: 1rpx solid #86efac;
}
.status-cancelled {
background: #fef2f2;
color: #dc2626;
border: 1rpx solid #fca5a5;
}
/* 时间网格 */
.time-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.time-item {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.time-label {
font-size: 24rpx;
color: #64748b;
font-weight: 500;
}
.time-value {
font-size: 26rpx;
color: #1e293b;
font-weight: 600;
}
/* 评价显示 */
.rating-display {
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
background: #f8fafc;
border-radius: 12rpx;
margin-bottom: 24rpx;
}
.stars-container {
display: flex;
gap: 8rpx;
margin-bottom: 12rpx;
}
.star {
font-size: 36rpx;
color: #e2e8f0;
transition: color 0.3s ease;
}
.star.active {
color: #f59e0b;
}
.rating-text {
font-size: 26rpx;
font-weight: 600;
color: #1e293b;
}
.rating-details {
display: flex;
flex-direction: column;
gap: 16rpx;
}
/* 空状态 */
.empty-card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 40rpx;
background: #f8fafc;
border-radius: 16rpx;
border: 2rpx dashed #e2e8f0;
margin-top: 24rpx;
}
.empty-icon {
font-size: 64rpx;
margin-bottom: 20rpx;
}
.empty-text {
font-size: 28rpx;
color: #64748b;
font-weight: 600;
margin-bottom: 8rpx;
}
.empty-desc {
font-size: 24rpx;
color: #94a3b8;
}
/* 响应式设计 */
@media (min-width: 768px) {
.ticket-modal-content {
width: 85%;
max-width: 650px;
}
}
/* 移动端适配 */
@media (max-width: 768px) {
.ticket-modal-mask {
padding: 20rpx;
}
.ticket-modal-content {
max-height: 80vh;
}
.ticket-modal-body {
max-height: calc(80vh - 120rpx);
}
.info-row {
flex-direction: column;
align-items: flex-start;
gap: 8rpx;
padding: 16rpx 0;
}
.info-value {
text-align: left;
margin-left: 0;
}
.time-grid {
grid-template-columns: 1fr;
gap: 16rpx;
}
.status-badge {
align-self: flex-start;
}
}
/* 滚动条优化 */
::-webkit-scrollbar {
width: 4rpx;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 2rpx;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 2rpx;
}
::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>