deotalandAi/apps/frontend/src/components/OrderProcessModal/index.vue

396 lines
8.6 KiB
Vue

<template>
<div v-if="show" class="order-process-overlay" @click="handleOverlayClick">
<div class="order-process-container" @click.stop>
<button class="close-button" @click="onClose" :aria-label="$t('common.close') || '关闭'">
<el-icon class="close-icon"><CloseBold /></el-icon>
</button>
<div class="process-header">
<h2 class="title">{{ $t('orderProcess.title') || '定制到家流程' }}</h2>
<p class="subtitle">{{ $t('orderProcess.subtitle') || '了解您的模型从制作到发货的全过程' }}</p>
</div>
<div class="process-timeline">
<div class="timeline-item" v-for="(step, index) in processSteps" :key="index">
<div class="timeline-marker">
<div class="marker-icon">
<el-icon><component :is="step.icon" /></el-icon>
</div>
<div v-if="index < processSteps.length - 1" class="marker-line"></div>
</div>
<div class="timeline-content">
<h3 class="step-title">{{ step.title }}</h3>
<p class="step-description">{{ step.description }}</p>
<div class="step-time">{{ step.time }}</div>
<!-- 缩略图显示 -->
<div v-if="step.hasThumbnail" class="step-thumbnail">
<img
src="https://draft-user.s3.us-east-2.amazonaws.com/images/4abf3da6-6abe-4604-adb9-554cf49d9743.webp"
alt="产品零件示例图"
class="thumbnail-image"
/>
<p class="thumbnail-caption">{{ t('orderProcess.steps.inspection.thumbnailCaption') }}</p>
</div>
</div>
</div>
</div>
<div class="process-note">
<el-icon class="note-icon"><InfoFilled /></el-icon>
<p>{{ $t('orderProcess.note') || '注意:以上时间为工作日计算,节假日可能会顺延。如有问题,请联系客服' }}</p>
</div>
<div class="actions">
<button class="acknowledge-btn" @click="handleAcknowledge">
{{ $t('orderProcess.acknowledge') || '我已知晓' }}
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElIcon } from 'element-plus'
import {
CloseBold,
InfoFilled,
CreditCard,
Document,
Calendar,
Setting,
Picture,
Van
} from '@element-plus/icons-vue'
const { t } = useI18n()
const props = defineProps({
show: { type: Boolean, default: false },
modelData: { type: Object, default: null }
})
const emit = defineEmits(['close', 'acknowledge'])
const handleOverlayClick = () => onClose()
const onClose = () => emit('close')
const handleAcknowledge = () => {
emit('acknowledge', props.modelData)
onClose()
}
// 订单流程步骤
const processSteps = computed(() => [
{
icon: CreditCard,
title: t('orderProcess.steps.payment.title'),
description: t('orderProcess.steps.payment.description'),
time: t('orderProcess.steps.payment.time')
},
{
icon: Document,
title: t('orderProcess.steps.review.title'),
description: t('orderProcess.steps.review.description'),
time: t('orderProcess.steps.scheduling.time')
},
{
icon: Calendar,
title: t('orderProcess.steps.scheduling.title'),
description: t('orderProcess.steps.scheduling.description'),
time: t('orderProcess.steps.scheduling.time')
},
// {
// icon: Setting,
// title: t('orderProcess.steps.production.title'),
// description: t('orderProcess.steps.production.description'),
// time: t('orderProcess.steps.production.time')
// },
{
icon: Picture,
title: t('orderProcess.steps.inspection.title'),
description: t('orderProcess.steps.inspection.description'),
time: t('orderProcess.steps.inspection.time'),
hasThumbnail: true
},
{
icon: Van,
title: t('orderProcess.steps.shipping.title'),
description: t('orderProcess.steps.shipping.description'),
time: t('orderProcess.steps.scheduling.time')
}
])
</script>
<style scoped>
.order-process-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(6px);
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
}
.order-process-container {
width: 90vw;
max-width: 800px;
max-height: 90vh;
background: var(--content-bg, #111827);
border: 1px solid var(--border-color, #374151);
border-radius: 16px;
color: var(--text-color, #f9fafb);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
}
.close-button {
position: absolute;
top: 12px;
right: 12px;
width: 40px;
height: 40px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.22);
background: rgba(17, 24, 39, 0.6);
color: #fff;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.close-icon {
color: #ffffff;
font-size: 18px;
}
.process-header {
padding: 24px 24px 16px;
text-align: center;
}
.title {
margin: 0;
font-size: 24px;
font-weight: 700;
color: #ffffff;
}
.subtitle {
margin: 8px 0 0;
font-size: 14px;
color: var(--text-secondary, #cbd5e1);
}
.process-timeline {
padding: 0 24px;
overflow-y: auto;
flex: 1;
/* Custom scrollbar for timeline */
scrollbar-width: thin;
scrollbar-color: rgba(167, 139, 250, 0.3) transparent;
}
/* Webkit scrollbar for timeline */
.process-timeline::-webkit-scrollbar {
width: 6px;
}
.process-timeline::-webkit-scrollbar-track {
background: transparent;
}
.process-timeline::-webkit-scrollbar-thumb {
background: rgba(167, 139, 250, 0.3);
border-radius: 3px;
}
.process-timeline::-webkit-scrollbar-thumb:hover {
background: rgba(167, 139, 250, 0.5);
}
.timeline-item {
display: flex;
margin-bottom: 24px;
position: relative;
}
.timeline-marker {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 16px;
}
.marker-icon {
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(139, 92, 246, 0.2);
border: 2px solid rgba(139, 92, 246, 0.5);
display: flex;
align-items: center;
justify-content: center;
color: #a78bfa;
font-size: 18px;
}
.marker-line {
width: 2px;
height: 60px;
background: rgba(139, 92, 246, 0.3);
margin-top: 8px;
}
.timeline-content {
flex: 1;
padding-top: 4px;
}
.step-title {
margin: 0 0 8px 0;
font-size: 18px;
font-weight: 600;
color: #ffffff;
}
.step-description {
margin: 0 0 8px 0;
font-size: 14px;
color: var(--text-secondary, #cbd5e1);
line-height: 1.5;
}
.step-time {
font-size: 13px;
color: #a78bfa;
font-weight: 500;
}
.step-thumbnail {
margin-top: 12px;
border-radius: 8px;
overflow: hidden;
border: 1px solid rgba(139, 92, 246, 0.2);
}
.thumbnail-image {
width: 100%;
height: auto;
display: block;
border-radius: 8px 8px 0 0;
}
.thumbnail-caption {
margin: 0;
padding: 8px 12px;
font-size: 12px;
color: var(--text-secondary, #cbd5e1);
text-align: center;
background: rgba(139, 92, 246, 0.05);
}
.process-note {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 16px 24px;
background: rgba(139, 92, 246, 0.1);
border-top: 1px solid rgba(139, 92, 246, 0.2);
}
.note-icon {
color: #a78bfa;
font-size: 18px;
margin-top: 2px;
}
.process-note p {
margin: 0;
font-size: 13px;
color: var(--text-secondary, #cbd5e1);
line-height: 1.5;
}
.actions {
display: flex;
justify-content: center;
padding: 16px 24px 24px;
}
.acknowledge-btn {
height: 44px;
padding: 0 24px;
border-radius: 10px;
border: 1px solid rgba(139, 92, 246, 0.35);
background: rgba(139, 92, 246, 0.25);
color: #fff;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.acknowledge-btn:hover {
background: rgba(139, 92, 246, 0.35);
border-color: rgba(139, 92, 246, 0.5);
}
/* 响应式设计 */
@media (max-width: 768px) {
.order-process-container {
width: 96vw;
max-height: 95vh;
}
.process-header {
padding: 20px 20px 12px;
}
.title {
font-size: 20px;
}
.subtitle {
font-size: 13px;
}
.process-timeline {
padding: 0 20px;
}
.timeline-item {
margin-bottom: 20px;
}
.marker-icon {
width: 36px;
height: 36px;
font-size: 16px;
}
.marker-line {
height: 50px;
}
.step-title {
font-size: 16px;
}
.step-description {
font-size: 13px;
}
.process-note {
padding: 12px 20px;
}
.actions {
padding: 12px 20px 20px;
}
.acknowledge-btn {
height: 40px;
padding: 0 20px;
font-size: 15px;
}
}
</style>