Files
ece.suwa3d.com/src/views/douyin/success.vue
T
Linzm 8e8cde9ef5
continuous-integration/drone/push Build was killed
continuous-integration/drone/promote/prod Build is passing
新增:抖音支持一个链接下多单;图片按裁剪3:4比例;根据返回的数据显示门店名称和门店logo
2026-10-27 14:34:49 +08:00

518 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="success-page">
<DyNavBar
theme="light"
title="提交成功"
right-text="生图记录"
border
@back="goHome"
@right="goRecords"
/>
<div class="body">
<div class="hero">
<div class="hero-rings" aria-hidden="true">
<span class="ring r1" />
<span class="ring r2" />
<span class="ring r3" />
</div>
<div class="hero-icon">
<span class="check">✓</span>
</div>
<h1 class="hero-title">提交制作成功</h1>
<p class="hero-desc">{{ heroDesc }}</p>
</div>
<div v-if="resultList.length === 1" class="thumb-card" @click="openZoom(0)">
<img :src="resultList[0].url" class="thumb-img" alt="已提交效果图">
<div class="thumb-badge">已提交</div>
<div v-if="resultList[0].count > 1" class="thumb-count">×{{ resultList[0].count }}</div>
</div>
<div v-else-if="resultList.length > 1" class="thumb-grid">
<div
v-for="(item, idx) in resultList"
:key="`${item.taskId || item.url}-${idx}`"
class="thumb-grid-item"
@click="openZoom(idx)"
>
<img :src="item.url" class="thumb-img" alt="已提交效果图">
<div class="thumb-badge">已提交</div>
<div v-if="item.count > 1" class="thumb-count">×{{ item.count }}</div>
<div v-if="item.label" class="thumb-label">{{ item.label }}</div>
</div>
</div>
<div class="steps-card">
<div class="steps-title">接下来会怎样</div>
<ul class="steps">
<li class="step is-done">
<span class="dot" />
<div>
<div class="step-name">确认效果并提交</div>
<div class="step-desc">已完成{{ totalCountText }}</div>
</div>
</li>
<li class="step is-active">
<span class="dot" />
<div>
<div class="step-name">进入制作流程</div>
<div class="step-desc">制作团队将按效果图开始生产</div>
</div>
</li>
<li class="step">
<span class="dot" />
<div>
<div class="step-name">完成后通知发货</div>
<div class="step-desc">请留意订单物流与消息通知</div>
</div>
</li>
</ul>
</div>
<div class="tips-card">
<div class="tips-title">温馨提示</div>
<p>本订单提交后不可再次上传生图,如需查看效果请前往生图记录。</p>
</div>
</div>
<div class="footer">
<button class="btn-ghost" type="button" @click="goHome">返回首页</button>
<button class="btn-primary" type="button" @click="goRecords">查看生成记录</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { showImagePreview } from 'vant'
import { douyinConfig as config } from '@/config/douyin'
import DyNavBar from './components/DyNavBar.vue'
type SuccessResult = {
url: string
count: number
label: string
taskId?: string
imageId?: string
}
const router = useRouter()
const resultList = ref<SuccessResult[]>([])
function normalizeResult(item: any): SuccessResult | null {
const url = String(item?.url || item?.path || item?.result_url || '').trim()
if (!url) return null
return {
url,
count: Math.max(1, Number(item?.count) || 1),
label: String(item?.label || item?.style || '').trim(),
taskId: item?.taskId ? String(item.taskId) : '',
imageId: item?.imageId ? String(item.imageId) : '',
}
}
function loadResults() {
const list: SuccessResult[] = []
try {
const raw = sessionStorage.getItem('douyin_success_results')
if (raw) {
const parsed = JSON.parse(raw)
if (Array.isArray(parsed)) {
parsed.forEach((item) => {
const normalized = normalizeResult(item)
if (normalized) list.push(normalized)
})
}
}
} catch (_) {}
if (!list.length) {
const single = sessionStorage.getItem('douyin_result_url') || ''
if (single) {
list.push({
url: single,
count: 1,
label: '',
})
}
}
// 去重,保留首次出现顺序
const seen = new Set<string>()
resultList.value = list.filter((item) => {
const key = `${item.taskId || ''}|${item.imageId || ''}|${item.url}`
if (seen.has(key)) return false
seen.add(key)
return true
})
}
const totalPieces = computed(() =>
resultList.value.reduce((sum, item) => sum + (Number(item.count) || 1), 0),
)
const heroDesc = computed(() => {
const n = resultList.value.length
if (n > 1) {
return `已提交 ${n} 张效果图(共 ${totalPieces.value} 件),可随时在生图记录中查看`
}
if (totalPieces.value > 1) {
return `已提交 ${totalPieces.value} 件制作,可随时在生图记录中查看`
}
return '效果图已确认并进入制作流程,可随时在生图记录中查看'
})
const totalCountText = computed(() => {
const n = resultList.value.length
if (n > 1) return ` · ${n} 张 / ${totalPieces.value} 件`
if (totalPieces.value > 1) return ` · ${totalPieces.value} 件`
return ''
})
function goHome() {
router.replace(config.routes.home)
}
function goRecords() {
router.replace(config.routes.records)
}
function openZoom(startIndex = 0) {
const images = resultList.value.map((item) => item.url).filter(Boolean)
if (!images.length) return
showImagePreview({
images,
startPosition: Math.max(0, Math.min(startIndex, images.length - 1)),
closeable: true,
closeIconPosition: 'top-right',
})
}
onMounted(() => {
loadResults()
})
</script>
<style scoped lang="scss">
.success-page {
min-height: 100vh;
min-height: 100dvh;
background: #f7faf6;
box-sizing: border-box;
padding-bottom: calc(88px + env(safe-area-inset-bottom));
}
.body {
padding: 8px 16px 24px;
animation: pageIn 0.55s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes pageIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.hero {
position: relative;
text-align: center;
padding: 28px 12px 20px;
overflow: hidden;
}
.hero-rings {
position: absolute;
left: 50%;
top: 42px;
width: 120px;
height: 120px;
transform: translateX(-50%);
pointer-events: none;
}
.ring {
position: absolute;
inset: 0;
border-radius: 50%;
border: 1px solid rgba(95, 191, 69, 0.28);
animation: pulse 2.4s ease-out infinite;
}
.r2 {
inset: -14px;
animation-delay: 0.35s;
opacity: 0.7;
}
.r3 {
inset: -28px;
animation-delay: 0.7s;
opacity: 0.4;
}
@keyframes pulse {
0% {
transform: scale(0.86);
opacity: 0.55;
}
70% {
transform: scale(1.08);
opacity: 0;
}
100% {
opacity: 0;
}
}
.hero-icon {
position: relative;
z-index: 1;
width: 72px;
height: 72px;
margin: 0 auto 16px;
border-radius: 50%;
background: linear-gradient(145deg, #8fd96f 0%, #5fbf45 100%);
box-shadow: 0 10px 24px rgba(95, 191, 69, 0.35);
display: flex;
align-items: center;
justify-content: center;
animation: pop 0.5s cubic-bezier(0.34, 1.4, 0.64, 1);
}
@keyframes pop {
from {
transform: scale(0.6);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
.check {
color: #fff;
font-size: 34px;
font-weight: 700;
line-height: 1;
}
.hero-title {
margin: 0;
font-size: 24px;
font-weight: 700;
color: #152016;
letter-spacing: 0.02em;
}
.hero-desc {
margin: 8px auto 0;
max-width: 300px;
font-size: 13px;
line-height: 1.55;
color: #7a8a76;
}
.thumb-card {
position: relative;
margin: 8px auto 14px;
width: min(100%, 220px);
aspect-ratio: 1;
border-radius: 18px;
overflow: hidden;
background: #fff;
box-shadow: 0 8px 22px rgba(40, 70, 50, 0.1);
cursor: pointer;
}
.thumb-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin: 4px 0 14px;
}
.thumb-grid-item {
position: relative;
aspect-ratio: 1;
border-radius: 16px;
overflow: hidden;
background: #fff;
box-shadow: 0 6px 18px rgba(40, 70, 50, 0.1);
cursor: pointer;
}
.thumb-img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
background: #fff;
}
.thumb-badge {
position: absolute;
left: 8px;
top: 8px;
padding: 3px 8px;
border-radius: 999px;
background: rgba(95, 191, 69, 0.92);
color: #fff;
font-size: 11px;
font-weight: 600;
}
.thumb-count {
position: absolute;
right: 8px;
top: 8px;
min-width: 28px;
padding: 3px 8px;
border-radius: 999px;
background: rgba(0, 0, 0, 0.55);
color: #fff;
font-size: 11px;
font-weight: 600;
text-align: center;
}
.thumb-label {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 18px 8px 8px;
font-size: 11px;
color: #fff;
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.55));
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.steps-card,
.tips-card {
background: #fff;
border-radius: 16px;
padding: 16px 16px 14px;
margin-bottom: 12px;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.04);
}
.steps-title,
.tips-title {
font-size: 15px;
font-weight: 700;
color: #1a1a1a;
margin-bottom: 12px;
}
.steps {
list-style: none;
margin: 0;
padding: 0;
}
.step {
position: relative;
display: flex;
gap: 12px;
padding: 0 0 16px;
}
.step:last-child {
padding-bottom: 0;
}
.step:not(:last-child)::before {
content: '';
position: absolute;
left: 7px;
top: 18px;
bottom: 0;
width: 2px;
background: #e6efe3;
}
.step .dot {
flex-shrink: 0;
width: 16px;
height: 16px;
margin-top: 2px;
border-radius: 50%;
background: #d7e4d3;
box-sizing: border-box;
border: 3px solid #eef5eb;
}
.step.is-done .dot,
.step.is-active .dot {
background: #5fbf45;
border-color: rgba(95, 191, 69, 0.22);
box-shadow: 0 0 0 3px rgba(95, 191, 69, 0.16);
}
.step-name {
font-size: 14px;
font-weight: 600;
color: #222;
}
.step-desc {
margin-top: 2px;
font-size: 12px;
color: #8a9586;
line-height: 1.4;
}
.tips-card p {
margin: 0;
font-size: 13px;
line-height: 1.55;
color: #6b7a66;
}
.footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 5;
display: flex;
gap: 10px;
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
background: linear-gradient(180deg, rgba(247, 250, 246, 0) 0%, #f7faf6 26%);
box-sizing: border-box;
}
.btn-ghost {
flex: 0 0 112px;
height: 48px;
border: none;
border-radius: 24px;
background: #e4efe0;
color: #3f9e36;
font-size: 15px;
font-weight: 600;
}
.btn-primary {
flex: 1;
height: 48px;
border: none;
border-radius: 24px;
background: linear-gradient(90deg, #7ed45a 0%, #5fbf45 100%);
color: #fff;
font-size: 16px;
font-weight: 700;
box-shadow: 0 6px 16px rgba(95, 191, 69, 0.32);
}
</style>