ece.suwa3d.com
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.
 
 
 
 

1470 lines
39 KiB

<template>
<div class="preview-container">
<view class="backstyle_1"></view>
<view class="backstyle_2"></view>
<div class="header" @click="goBack">
<span class="back-icon"><van-icon name="arrow-left" size="16px" /> 效果预览</span>
</div>
<div class="step-container">
<div class="step-item">
<div class="step-content">
<div class="step-title">正面照片</div>
<div class="step-desc">清晰的正面照片</div>
</div>
</div>
<div class="step-item active">
<div class="step-num">2</div>
<div class="step-content">
<div class="step-title">确认下单</div>
<div class="step-desc">选择一个你喜欢的效果图下单</div>
</div>
</div>
</div>
<div v-if="!imageUrl" class="progress-section">
<div class="progress-bar-bg">
<div class="progress-bar-fg" :style="{ width: progress + '%' }"></div>
</div>
<div class="progress-text">{{progressText}} {{progress}}%</div>
<div class="progress-desc">总计大约需要90秒,请耐心等待...</div>
</div>
<div v-else class="progress-section-picture" style="width: 320px;height: 320px;">
<div class="progress-section-img" style="width: 320px;height: 320px;">
<img v-if="shapeImage" class="box1-back-image" :src="shapeImage" style="width: 320px;height: 320px;" alt="">
<div class="box1-front-box" :style="getStyle">
<img class="box1-front-image" :src="imageUrl" alt="" style="max-height: 320px;">
<!-- <div class="box1-round-shadow" v-if="shapeId == 1 && shapeImage == ''" style="width: 320px;height: 320px;"></div> -->
</div>
<div class="shape-text" :style="shapeTextStyle" v-if="shapeText">{{shapeText}}</div>
</div>
</div>
<div class="image-list-box" v-if="imageList.length > 1">
<div class="image-list-item" v-for="item in imageList" :key="item.key">
<img v-if="item.status == 1" class="image-list-item-img" :class="{ imgActive: item.key == imgKey }" :src="item.origin_url" alt="" @click="changeImage(item)">
<div v-else-if="item.status == 0" class="image-list-item-loading">
设计中
</div>
<div v-else-if="item.status == 2" class="image-list-item-loading">
设计失败
</div>
</div>
</div>
<div class="info-section">
<div class="info-item">
<div class="info-title" v-if="typeId == 1">{{ prodId == 7 ? '人物立体徽章' : '宠物立体徽章' }}</div>
<div class="info-title" v-if="typeId == 2">{{ prodId == 7 ? '人物浮雕相框' : '宠物浮雕相框' }}</div>
<div class="info-title" v-if="typeId == 3">{{ prodId == 7 ? '人物3D冰箱贴' : '宠物3D冰箱贴' }}</div>
<div class="info-title" v-if="typeId == 4">{{ prodId == 1 ? '人物卡通手办' : '宠物卡通手办' }}</div>
<div class="info-content">产品类型</div>
</div>
<div class="info-item">
<div class="info-title">3D全彩打印</div>
<div class="info-content">工艺</div>
</div>
<div class="info-item">
<div class="info-title">ID</div>
<div class="info-content">{{ pid }}</div>
</div>
</div>
<div class="shape-body">
<div class="shape-type" v-if="imageUrl && typeId == 3">
<div class="shape-type-item" :class="{ 'shape-active': item.id == shapeId }" v-for="item in shapeList" :key="item.id" @click="shapeChange(item)">{{item.name}}</div>
</div>
<div class="shape-box" v-if="imageUrl && typeId != 3 && typeId != 4">
<block v-for="item in shapeList" :key="item.id">
<div class="shape-item" @click="shapeChange(item)">
<div class="shape-item-list">
<img class="shape-item-image" :class="{ 'shape-item-image-round': item.id == 1 || item.id == 2 }" :src="item.cover_path || imageUrl"/>
<van-icon v-if="item.id == shapeId" class="shape-icon" color="#15CF5F" name="checked" size="18px" />
</div>
<div class="shape-item-text">{{item.name}}</div>
</div>
</block>
</div>
<div class="shape-box-input" v-if="imageUrl && custom_switch == 1">
<input class="shape-box-input-text" type="text" placeholder="请输入文字" v-model="shapeText" @change="changeShapeText" @input="changeShapeText" @onBlur="changeShapeText" />
</div>
</div>
<!-- <div class="info-desc">
下单后预计需要7天完成生产发货。<br>
实物3D打印色彩与图片有合理色差,如对产品有疑问,可以联系客服咨询。
</div> -->
<div style="padding: 0 16px;">
<van-divider />
</div>
<div class="order-section" v-if="trialCode == false && imageUrl">
<div class="order-title">订购数量</div>
<block v-for="item in sizeList" :key="item.id">
<block>
<div class="order-item">
<span class="order-size">{{ item.size }}</span>
<span class="order-free">(剩余兑换:{{ item.remaining }})</span>
<div class="order-ctrl">
<van-stepper v-model="item.count" :min="0" :max="item.remaining" @change="changeValue" />
</div>
</div>
</block>
<!-- <block v-if="orderStat.use_type == 2">
<div class="order-item">
<span class="order-size">{{ item.size }}</span>
<span class="order-free">(剩余兑换:{{ item.remaining }})</span>
<div class="order-ctrl">
<van-stepper v-model="item.count" :min="0" :max="item.remaining" @change="changeValue" />
</div>
</div>
</block> -->
</block>
</div>
<div class="address-box" v-if="orderStat.use_type == 2 && imageUrl && code == 'ACAYOAAC3PFCPO3CD6DVM1'">
<div class="order-title" style="padding: 0 16px;">地址识别</div>
<div class="address-item">
<van-field
v-model="addressInput"
label=""
placeholder="请输入姓名、手机号、详细地址(如:张三 13800138000 广东省深圳市南山区科技园1号)"
clearable
right-icon="search"
>
<template #button>
<van-button size="small" type="primary" @click="handleAddressInput" style="margin-left: 8px;">自动识别</van-button>
</template>
</van-field>
</div>
<div class="address-item" style="padding: 0px 16px 16px 16px;" v-if="contact_name && contact_mobile && province_name && city_name && county_name && address">
<div style="font-size: 16px;color: #387aff;text-align: left;margin-bottom: 8px;">识别结果</div>
<div style="font-size: 14px;text-align: left;">
{{ contact_name }} {{ contact_mobile }} {{ province_name }} {{ city_name }} {{ county_name }} {{ address }}
</div>
</div>
</div>
<div class="address-box" v-if="orderStat.use_type == 2 && imageUrl">
<div class="order-title" style="padding: 0 16px;">收货地址</div>
<div class="address-item">
<van-address-edit
:area-list="areaList"
:tel-validator="true"
tel-maxlength="11"
show-delete
show-search-result
:search-result="searchResult"
:area-columns-placeholder="['请选择', '请选择', '请选择']"
@change="nameTelChange"
@change-area="changeArea"
@change-detail="onChangeDetail"
/>
</div>
</div>
<div style="height: 130px;"></div>
<div class="confirm-box">
<div class="action-section">
<button v-if="trialCode == false" @click="sureReload" :disabled="flag < 1" class="action-btn"><img class="action-img" src="@/assets/badge/reload.png" alt=""> 再次生成</button>
<button @click="compare" :disabled="flag < 1" class="action-btn"><img class="action-img" src="@/assets/badge/duibi.png" alt=""> 前后对比</button>
<button @click="save" :disabled="flag < 1" class="action-btn"><img class="action-img" src="@/assets/badge/down.png" alt=""> 保存图片</button>
</div>
<div class="btn-box" v-if="trialCode == false">
<button @click="confirm" :disabled="flag < 1" class="confirm-btn">确认选择</button>
</div>
</div>
</div>
<van-action-sheet v-model:show="showCompare" title="前后对比" :close-on-click-overlay="true" closeable>
<div style="padding: 16px;">
<div style="display: flex; align-items: center; justify-content: center; gap: 20px;">
<div style="text-align: center;">
<img :src="compareList.origin_path" alt="原图" style="max-width: 120px; max-height: 120px; border-radius: 8px;">
</div>
<div style="font-size: 24px; color: #333;">
<van-icon name="arrow" size="20px" />
</div>
<div style="text-align: center;">
<img :src="imageUrl" alt="效果图" style="width: 120px; height: 120px; border-radius: 8px;">
</div>
</div>
<div style="text-align: center; margin-top: 16px; color: #999; font-size: 12px;">
温馨提示:<br/>
AI设计的徽章效果图是根据用户提供的照片机器学习生成的,<br/>
多试几次就能找到你满意的徽章~
</div>
</div>
</van-action-sheet>
<div v-if="showPreview">
<div class="preview-mask" @click="showPreview = false">
<img :src="imageWater" alt="徽章预览" />
<p>长按图片 → 保存到相册</p>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, onUnmounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { showSuccessToast, showToast, showNotify, showConfirmDialog, AddressEdit, Area } from 'vant';
import { localStorage } from '@/utils/local-storage'
import * as badgeApi from '@/api/badge'
import { areaList } from '@/utils/area'
const router = useRouter();
const value = ref(0);
const showReload = ref(false)
const showCompare = ref(false)
const showBgCompare = ref(false)
const currentIndex = ref(0)
const imageList = ref([])
const imageWater = ref('')
const code = localStorage.get('code')
function compare() {
showCompare.value = true
}
function sureReload() {
console.log('orderStat', orderStat.value)
if (orderStat.value.remain_count <= 0) {
showToast('当前次数已用完,请重新购买')
return
}
isPreview.value = false
showConfirmDialog({
title: '再次生成',
message:
'请确认是否重新生成',
})
.then(() => {
group.value = newGroup.value
const params = {
pid: pid.value,
group: 1,
prod_id: prodId.value,
type_id: typeId.value,
kind_id: kindId.value
}
badgeApi.putModeling(params).then((res: any) => {
console.log('putModeling', res)
imageUrl.value = '';
imageList.value = []
imgKey.value = '';
createLog()
}).catch((err) => {
console.log('putModeling', err)
showReload.value = false
showToast({
message: err.message,
duration: 2000,
})
}).finally(() => {
showReload.value = false
})
})
.catch(() => {
showReload.value = false
});
}
const imgKey = ref(101)
const frameUrl = ref('')
const changeImage = (item: any) => {
console.log('changeImage', item)
imgKey.value = item.key
imageUrl.value = shapeId.value == 17 || shapeId.value == 18 ? item.frame_url : item.origin_url
frameUrl.value = item.frame_url
imageWater.value = item.image_url
}
const createLog = () => {
badgeApi.createLog({
pid: pid.value,
group: 1,
prod_id: prodId.value,
type_id: typeId.value,
kind_id: kindId.value
}).then((res: any) => {
console.log('createLog', res)
showReload.value = false
progressTimer.value = null
getImageList()
progressList()
getCompareImage()
timer.value = setInterval(() => {
getImageList()
}, 10000)
}).catch((err) => {
console.log('createLog', err)
showToast({
message: err.message,
duration: 2000,
})
}).finally((err) => {
showReload.value = false
})
}
const showPreview = ref(false)
function save() {
showPreview.value = true;
}
const compareList = ref({})
const style_name = ref('')
function getCompareImage() {
badgeApi.getCompareImage({
pid: pid.value,
group: 1,
prod_id: prodId.value,
type_id: typeId.value
}).then((res: any) => {
console.log('getCompareImage', res)
compareList.value = res
style_name.value = res.list[0].kind_name || res.kind_name
}).catch((err) => {
console.log('getCompareImage', err)
showToast({
message: err.message,
duration: 2000,
})
}).finally((err) => {
})
}
function goBack() {
router.back()
}
const sizeList = ref([])
const shapeItem = ref({})
const getSizeList = () => {
badgeApi.getOrderPrice({}).then((res: any) => {
console.log('getSizeList', res);
sizeList.value = res
if (typeId.value != 4) {
getShapeList()
}
})
}
const contact_name = ref('')
const contact_mobile = ref('')
const province_id = ref(0)
const city_id = ref(0)
const county_id = ref(0)
const province_name = ref('')
const city_name = ref('')
const county_name = ref('')
const address = ref('')
const nameTelChange = (res: any) => {
console.log('nameTelChange', res)
if (res.key == 'name') {
contact_name.value = res.value
} else {
contact_mobile.value = res.value
}
}
const changeArea = (value: any) => {
console.log('changeArea', value)
province_id.value = value[0].value
city_id.value = value[1].value
county_id.value = value[2].value
province_name.value = value[0].text
city_name.value = value[1].text
county_name.value = value[2].text
}
const onChangeDetail = (value: any) => {
console.log('onChangeDetail', value)
address.value = value
}
const payAmount = ref(0)
const changeValue = (value: number) => {
console.log(value)
payAmount.value = value
}
const addressInput = ref('')
const handleAddressInput = () => {
console.log('handleAddressInput', addressInput.value)
const text = addressInput.value;
if (!text) return;
// 识别手机号
const phoneRegex = /(1[3-9]\d{9})/g;
const phoneMatch = phoneRegex.exec(text);
if (phoneMatch) {
contact_mobile.value = phoneMatch[0];
}
// 简单识别姓名(2-4个中文字符)
const nameRegex = /([\u4e00-\u9fa5]{2,4})/g;
const nameMatches = text.match(nameRegex);
if (nameMatches && nameMatches.length > 0) {
// 假设第一个匹配的中文名是姓名
contact_name.value = nameMatches[0];
}
console.log('contact_name', contact_name.value)
console.log('contact_mobile', contact_mobile.value)
// 识别省份
let provinceFound = '';
for (const [provinceId, provinceName] of Object.entries(areaList.province_list)) {
if (text.includes(provinceName)) {
provinceFound = provinceName;
console.log('识别到省份ID:', provinceId);
province_id.value = provinceId;
break;
}
}
console.log('province_id', province_id.value)
console.log('provinceFound', provinceFound)
province_name.value = provinceFound;
// 识别城市
let cityFound = '';
for (const [cityId, cityName] of Object.entries(areaList.city_list)) {
if (text.includes(cityName) && cityId !== province_id.value) {
cityFound = cityName;
city_id.value = cityId;
break;
}
}
console.log('cityFound', cityFound)
city_name.value = cityFound;
// 提取详细地址(从文本中移除已识别的部分)
let detail = text
.replace(contact_name.value, '')
.replace(contact_mobile.value, '')
.replace(province_name.value, '')
.replace(city_name.value, '')
.trim();
// 移除可能的区级信息(简单处理)
if (detail.includes('区') || detail.includes('县')) {
const districtMatch = detail.match(/(.+?(区|县))/);
if (districtMatch) {
// 遍历区县列表,查找匹配的区县名,获取对应的区id
let foundCountyId = '';
for (const [cid, cname] of Object.entries(areaList.county_list)) {
if (districtMatch[0].includes(cname)) {
foundCountyId = cid;
county_id.value = cid;
county_name.value = cname;
break;
}
}
// 如果没有在区县列表中找到,仍然保留原有逻辑
if (!foundCountyId) {
county_id.value = 0;
county_name.value = '';
}
detail = detail.replace(county_name.value, '').trim();
}
}
console.log('county_id', county_id.value)
console.log('county_name', county_name.value)
console.log('detail', detail)
address.value = detail;
}
const key = ref(0)
const loading = ref(false)
const confirm = () => {
if (loading.value) return
console.log('confirm')
if (payAmount.value <= 0) {
showToast('请先选择下单数量')
return
}
if (orderStat.value.use_type == 2 && !contact_name.value) {
showToast('请先填写收货人信息')
return
}
if (orderStat.value.use_type == 2 && !contact_mobile.value) {
showToast('请先填写收货人手机号')
return
}
if (orderStat.value.use_type == 2 && !province_id.value) {
showToast('请先选择地区')
return
}
if (orderStat.value.use_type == 2 && !address.value) {
showToast('请先填写详细地址')
return
}
getPosition()
loading.value = true
showConfirmDialog({
title: '确认下单',
message:
'请再次确认是否选择这个模型下单,下单后预计需要10天完成生产发货。如对产品有疑问,可以联系客服咨询。',
})
.then(() => {
const parms = {
pid: pid.value,
key: imgKey.value,
pay_amount: payAmount.value,
products: sizeList.value,
prod_id: prodId.value,
shape_id: shapeId.value,
custom_text: shapeText.value
}
if (orderStat.value.use_type == 2) {
parms.province_id = province_id.value
parms.city_id = city_id.value
parms.county_id = county_id.value
parms.province_name = province_name.value
parms.city_name = city_name.value
parms.county_name = county_name.value
parms.address = address.value
parms.contact_name = contact_name.value
parms.contact_mobile = contact_mobile.value
parms.addr_id = 0
parms.country_id = 45
}
console.log('parms', parms)
badgeApi.creatOrder(parms).then((res: any) => {
console.log('creatOrder', res)
showSuccessToast({
message: '下单成功',
duration: 2000,
})
router.push({
path: '/badge/myOrder'
})
loading.value = false
}).catch((err) => {
console.log('creatOrder', err)
loading.value = false
showToast({
message: err.message,
duration: 2000,
})
}).finally(() => {
loading.value = false
})
})
.catch(() => {
loading.value = false
// on cancel
});
}
const getPosition = () => {
badgeApi.composite({
pid: pid.value,
num: imgKey.value,
custom_text: shapeText.value,
shape_id: shapeId.value,
}).then((res: any) => {
console.log('getPosition', res)
}).catch((err) => {
console.log('getPosition', err)
}).finally((err) => {
})
}
const imageUrl = ref('')
const originUrl = ref('')
const group = ref(1);
// 轮询获取图片
const flag = ref(1)
const timer = ref()
const progress = ref(0)
const progressTimer = ref()
const progressText = ref('')
const progressList = () => {
// 启动进度条计时器
progress.value = 0
if (!progressTimer.value) {
progressTimer.value = setInterval(() => {
if (progress.value < 90) {
progress.value += 1
if (progress.value < 25) {
progressText.value = '照片数据分析中'
} else if (progress.value < 50) {
progressText.value = '提炼图像关键词'
} else if (progress.value < 75) {
progressText.value = '图像生成中'
} else {
progressText.value = '图像后处理与优化'
}
}
}, 300)
}
}
const newGroup = ref(0)
const isPreview = ref(false)
const getImageList = () => {
badgeApi.getImageList({
pid: pid.value,
group: 1,
prod_id: prodId.value
}).then((res: any) => {
console.log('getImageList', res)
const data = res || []
flag.value = data.flag
if (data.flag === 1) {
newGroup.value = data.next_group
nextTick(() => {
// 更新图片列表
const newList = data.list.map(item => ({
...item,
isNew: true
}))
// 找到第一个状态为1(已生成)的图片
const firstGeneratedImage = newList.find(item => item.status === 1)
// 如果有已生成的图片且还未设置预览图,则显示第一个生成的图片
if (firstGeneratedImage && !isPreview.value) {
originUrl.value = firstGeneratedImage.origin_url
imageUrl.value = firstGeneratedImage.origin_url
frameUrl.value = firstGeneratedImage.frame_url
imageWater.value = firstGeneratedImage.url
imgKey.value = firstGeneratedImage.key
isPreview.value = true
}
// 合并新旧列表,保持原有顺序
const mergedList = imageList.value.map(item => {
const newItem = newList.find(n => n.key === item.key)
return newItem || item
})
// 添加新图片
newList.forEach(newItem => {
if (!mergedList.find(item => item.key === newItem.key)) {
mergedList.push(newItem)
}
})
// 更新图片列表
imageList.value = mergedList
// 如果已有选中图片,保持显示当前选中的图片
console.log('imgKey.value', imgKey.value)
if (imgKey.value) {
const currentImage = mergedList.filter((item: any) => item.key === imgKey.value)[0]
console.log('currentImage', currentImage)
if (currentImage && currentImage.status === 1) {
originUrl.value = currentImage.origin_url
imageUrl.value = currentImage.origin_url
frameUrl.value = currentImage.frame_url
imageWater.value = currentImage.url
isPreview.value = true
} else {
originUrl.value = currentImage.origin_url
imageUrl.value = currentImage.origin_url
frameUrl.value = currentImage.frame_url
imageWater.value = currentImage.url
isPreview.value = true
}
}
})
}
if (data.flag === 2) {
newGroup.value = data.next_group
// 清除轮询和进度条计时器
clearInterval(timer.value)
clearInterval(progressTimer.value)
// 进度条到100%
progress.value = 100
// 显示图片
imageList.value = data.list
if (imgKey.value) {
originUrl.value = data.list.filter((item: any) => item.key == imgKey.value)[0].origin_url
imageUrl.value = data.list.filter((item: any) => item.key == imgKey.value)[0].origin_url
frameUrl.value = data.list.filter((item: any) => item.key == imgKey.value)[0].frame_url
imageWater.value = data.list.filter((item: any) => item.key == imgKey.value)[0].url
} else {
originUrl.value = data.list[0].origin_url
imageUrl.value = data.list[0].origin_url
frameUrl.value = data.list[0].frame_url
imageWater.value = data.list[0].url
imgKey.value = data.list[0].key
}
localStorage.remove('userId')
}
}).catch((err) => {
// 清除轮询和进度条计时器
// clearInterval(timer.value)
// clearInterval(progressTimer.value)
// progress.value = 0
showToast({
message: err.message,
duration: 2000,
})
}).finally((err) => {
})
}
const shapeId = ref(0)
const shapeImage = ref('')
const shapeList = ref([])
const custom_switch = ref(0)
const limitCount = ref(0)
const getShapeList = () => {
badgeApi.getShapeList({
prod_id: prodId.value,
type_id: typeId.value
}).then((res: any) => {
console.log('getShapeList', res)
shapeList.value = res.list
shapeId.value = res.list[0].id
shapeImage.value = res.list[0]?.frame_path
custom_switch.value = res.list[0]?.custom_switch
limitCount.value = res.list[0]?.text_limit_max
ImageShow(res.list[0])
if (custom_switch.value == 1) {
textShow(res.list[0])
}
}).catch((err) => {
console.log('getShapeList', err)
}).finally((err) => {
})
}
const getStyle = ref('')
const ImageShow = (item: any) => {
const scale = 0.6;
const img = new Image()
img.src = item.frame_path
img.onload = () => {
console.log('img', img)
const ratioWidth = 320 / img.width;
const ratioHeight = 320 / img.height;
const x = item.axisx * ratioWidth * scale;
const y = item.axisy * ratioHeight * scale;
const path_width = item.width * ratioWidth * scale;
const path_height = item.height * ratioHeight * scale;
getStyle.value = `left: ${x}px;top: ${y}px;width: ${path_width}px;height: ${path_height}px;`
}
}
const shapeTextStyle = ref('')
const textShow = (item: any) => {
const scale = 0.6;
const img = new Image()
img.src = item.frame_path
img.onload = () => {
console.log('img', img)
const ratioWidth = 320 / img.width;
const ratioHeight = 320 / img.height;
const x = item.text_axisx * ratioWidth * scale;
const y = item.text_axisy * ratioHeight * scale;
const path_width = item.text_width * ratioWidth * scale;
const path_height = item.text_height * ratioHeight * scale;
const text_size = item.font_size * Math.min(ratioWidth * scale, ratioHeight * scale);
shapeTextStyle.value = `left: ${x}px;top: ${y}px;width: ${path_width}px;height: ${path_height}px;text-align: center;line-height: ${path_height}px;font-size: ${text_size}px;color: ${item.font_color};font-weight: ${item.font_weight};`
}
}
const shapeText = ref('')
const changeShapeText = (e: any) => {
console.log('changeShapeText', e.target.value)
// if (!validateInput(e.target.value)) {
// showToast('输入的文字不能包含特殊符号!')
// shapeText.value = e.target.value.replace(/[^a-zA-Z0-9\u4e00-\u9fa5\s]/g, '');
// return;
// }
handleInput(e.target.value)
}
const validateInput = (input: string) => {
// 正则说明:只允许字母(大小写)、数字、中文和空格
const regex = /^[a-zA-Z0-9\u4e00-\u9fa5\s]+$/;
return regex.test(input);
}
const handleInput = (value: string) => {
let byteLength = calculateByteLength(value);
console.log('byteLength', byteLength, limitCount.value)
if (byteLength > limitCount.value) {
let validValue = '';
let currentBytes = 0;
for (let i = 0; i < value.length; i++) {
const char = value.charAt(i);
const code = char.charCodeAt(0);
let charBytes = 0;
if (code === 32) {
charBytes = 1;
} else {
charBytes = code > 255 ? 2 : 1;
}
if (currentBytes + charBytes <= limitCount.value) {
validValue += char;
currentBytes += charBytes;
} else {
break;
}
}
shapeText.value = validValue;
return;
}
shapeText.value = value;
}
const calculateByteLength = (str: string) => {
let len = 0;
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
if (code === 32) {
len += 1;
} else {
len += code > 255 ? 2 : 1;
}
}
return len;
}
const shapeChange = (item: any) => {
console.log('shapeChange', item)
sizeList.value.map((item: any) => {
if (item.shape_id !== item.id) {
item.count = 0
}
})
shapeText.value = '';
shapeId.value = item.id
shapeImage.value = item.frame_path
custom_switch.value = item.custom_switch
limitCount.value = item.text_limit_max
if (typeId.value == 3) {
imageUrl.value = item.id == 15 || item.id == 16 ? originUrl.value : frameUrl.value
} else {
imageUrl.value = originUrl.value
}
ImageShow(item)
if (custom_switch.value == 1) {
textShow(item)
}
}
const trialCode = ref(false)
const getTrialCode = () => {
const value = localStorage.get('trialCode')
if (value) {
trialCode.value = true
} else {
getOrderStat()
}
}
const orderStat = ref({})
const typeId = ref(0)
const getOrderStat = () => {
badgeApi.getOrderStat({}).then((res: any) => {
console.log('getOrderStat', res)
orderStat.value = res
typeId.value = res.type_id
prodId.value = res.prod_id
getSizeList()
})
}
function handleBeforeUnload(_event: BeforeUnloadEvent) {
localStorage.remove('code')
localStorage.remove('trialCode')
localStorage.remove('userId')
// 清除轮询和进度条计时器
clearInterval(timer.value)
clearInterval(progressTimer.value)
}
const pid = ref(0)
const route = useRoute()
const prodId = ref(0)
const kindId = ref(0)
onMounted(() => {
pid.value = route.query.pid
group.value = route.query.group
if (route.query.key) {
imgKey.value = route.query.key
}
if (route.query.prod_id) {
prodId.value = route.query.prod_id
}
if (route.query.kind_id) {
kindId.value = Number(route.query.kind_id)
}
getTrialCode()
// pid.value = 281505;
// group.value = 1;
// prodId.value = 7;
// getTypesList()
getImageList()
progressList()
setTimeout(() => {
getCompareImage()
}, 1000)
timer.value = setInterval(() => {
getImageList()
}, 10000)
window.addEventListener('beforeunload', handleBeforeUnload);
})
onUnmounted(() => {
clearInterval(timer.value)
clearInterval(progressTimer.value)
})
</script>
<style scoped>
.preview-container {
min-height: 100vh;
font-family: 'PingFang SC', Arial, sans-serif;
color: #222;
}
.backstyle_1 {
position: fixed;
left: -110px;
top: -40px;
width: 328px;
height: 216px;
opacity: 0.2;
transform: rotate(-13.72deg);
border-radius: 50%;
/* background: radial-gradient(48.48% 50% at 50% 50%, #EBD4A0 0%, #ebd5a000 81.94%); */
background-image: radial-gradient( rgba(250, 182, 55, 0.726), #ebd5a09c, #d4cab100);
z-index: -10;
}
.backstyle_2 {
position: fixed;
left: 97px;
top: -95px;
width: 370px;
height: 374px;
opacity: 0.1;
border-radius: 50%;
/* background: radial-gradient(59.73% 50% at 50% 50%, #90F272 0%, #90f27200 84.03%); */
background-image: radial-gradient( rgb(69, 255, 63), #90f2729a, #90f27200);
z-index: -10;
}
.header {
display: flex;
align-items: center;
height: 40px;
font-size: 14px;
color: #999;
padding: 16px 16px 0 16px;
}
.back-icon {
margin-right: 8px;
color: #222;
font-size: 16px;
cursor: pointer;
}
.step-container {
display: flex;
align-items: flex-start;
padding: 16px 16px 16px 0;
}
.step-item {
display: flex;
align-items: center;
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.step-item.active {
flex: 2.5;
}
.step-num {
font-size: 48px;
line-height: 1;
position: relative;
margin-right: 8px;
text-shadow: 0 2px 8px #e6f7e6;
color: #CCCCCC;
}
.step-item.active .step-num {
color: #fff;
text-shadow:
1px 1px 0 #000,
-1px -1px 0 #000,
-1px 1px 0 #000,
1px -1px 0 #000,
0 1px 0 #000,
1px 0 0 #000,
0 -1px 0 #000,
-1px 0 0 #000;
}
.step-item.active .step-num::after {
content: '';
position: absolute;
top: 20px;
right: 0;
width: 15px;
height: 15px;
background-color: #50cf54;
opacity: 0.5;
border-radius: 50%;
}
.step-content {
display: flex;
flex-direction: column;
height: 48px;
}
.step-item.active .step-title {
color: #000;
font-weight: bold;
font-size: 16px;
}
.step-item.active .step-desc {
color: #808080;
font-size: 13px;
}
.step-title {
font-size: 16px;
color: #808080;;
}
.step-desc {
font-size: 12px;
color: #808080;
margin-top: 2px;
}
.progress-section {
margin: 0 auto;
text-align: center;
width: 100vw;
height: 100vw;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.progress-section-picture {
width: 320px;
height: 320px;
position: relative;
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.progress-section-img {
position: relative;
display: flex;
justify-content: center;
}
.box1-back-image {
position: absolute;
top: 0;
left: 0;
z-index: 2;
object-fit: cover;
border-radius: 10px;
}
.box1-front-box {
position: absolute;
z-index: 1;
}
.box1-front-image {
width: 100%;
height: auto;
border-radius: 10px;
background: rgba(0, 0, 0, 0.3);
}
.box1-round-shadow {
position: absolute;
top: 0;
left: 0;
width: 320px;
height: 320px;
border-radius: 50%;
box-shadow: 2px 2px 4px #00000042,inset -2px -2px 4px #00000048,inset 2px 2px 4px #ffffffb4;
z-index: 10;
}
.box1-round-image {
border-radius: 50%;
}
.progress-bar-bg {
width: 70%;
height: 10px;
background: #eee;
border-radius: 5px;
margin: 0 auto 8px auto;
position: relative;
overflow: hidden;
}
.progress-bar-fg {
height: 100%;
background: #6fdc8c;
border-radius: 5px;
transition: width 0.3s;
}
.progress-text {
font-size: 15px;
color: #222;
margin-bottom: 2px;
}
.progress-desc {
font-size: 12px;
color: #999;
}
.info-section {
display: flex;
justify-content: flex-start;
padding: 16px;
}
.info-item {
flex: 1;
}
.info-title {
font-size: 13px;
color: #222;
font-weight: 600;
margin-bottom: 2px;
}
.info-content {
font-size: 12px;
color: #999;
}
.info-desc {
font-size: 12px;
color: #999;
margin-bottom: 18px;
line-height: 1.5;
text-align: left;
padding: 0 16px;
}
.order-section {
border-radius: 10px;
padding: 0 16px;
margin-bottom: 18px;
}
.order-title {
font-size: 14px;
color: #222;
font-weight: 600;
margin-bottom: 10px;
}
.order-item {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.order-size {
font-size: 15px;
color: #222;
margin-right: 8px;
}
.order-free {
font-size: 12px;
color: #999;
margin-right: 16px;
}
.order-ctrl {
display: flex;
align-items: center;
margin-left: auto;
}
.order-btn {
width: 26px;
height: 26px;
border: none;
background: #e8e8e8;
color: #bbb;
font-size: 18px;
border-radius: 4px;
margin: 0 4px;
cursor: pointer;
}
.order-btn:disabled {
background: #f2f2f2;
color: #ddd;
cursor: not-allowed;
}
.order-count {
font-size: 15px;
color: #222;
width: 22px;
text-align: center;
}
.confirm-box {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #fff;
z-index: 10;
padding: 0 16px 16px 16px;
border-top: 1px solid #e6e6e6;
}
.action-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding: 0 16px;
}
.action-btn {
flex: 1;
margin: 0 4px;
border: none;
border-radius: 8px;
color: #222;
font-size: 13px;
padding: 8px 0;
cursor: pointer;
transition: background 0.2s;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
}
.action-img {
width: 20px;
height: 20px;
margin-right: 5px;
}
.btn-box {
display: flex;
justify-content: center;
align-items: center;
}
.confirm-btn {
width: 80vw;
background: linear-gradient(90deg, #D1ED8E 0%, #55E668 100%);
color: #000;
font-size: 17px;
font-weight: 600;
border: none;
border-radius: 24px;
padding: 12px 0;
cursor: pointer;
box-shadow: 0 2px 8px rgba(111,220,140,0.08);
letter-spacing: 2px;
}
.confirm-btn:active {
background: linear-gradient(90deg, #5ccf7a 0%, #8fdca0 100%);
}
.style-selection {
padding: 16px;
}
.style-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.style-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
position: relative;
width: 28vw;
}
.style-img {
width: 28vw;
height: 28vw;
border-radius: 8px;
object-fit: cover;
background: #f5f5f5;
border: 2px solid transparent;
transition: border 0.2s;
}
.style-item .style-selected {
position: absolute;
right: 4px;
bottom: 25px;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
}
.style-label {
font-size: 13px;
color: #333;
margin-top: 4px;
}
.confirm-wrapper {
margin-top: 24px;
}
.confirm-button {
width: 100%;
background: linear-gradient(90deg, #D1ED8E 0%, #55E668 100%);
color: #000;
font-size: 17px;
font-weight: 600;
border: none;
border-radius: 24px;
padding: 12px 0;
cursor: pointer;
}
.confirm-button:active {
background: linear-gradient(90deg, #5ccf7a 0%, #8fdca0 100%);
}
.style-item.active .style-img {
border: 2px solid #50cf54;
}
.style-item.active .style-label {
color: #50cf54;
}
.preview-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
}
.preview-mask img {
max-width: 80%;
max-height: 80%;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
}
.preview-mask p {
margin-top: 16px;
color: #fff;
font-size: 16px;
text-align: center;
}
.progress-image-item-shadow {
position: absolute;
top: 0;
left: 0;
width: 80vw;
height: 80vw;
object-fit: cover;
box-shadow: inset 2px 2px 4px rgba(255, 255, 255, 0.5),inset -2px -2px 4px rgba(0, 0, 0, 0.2);
border-radius: 50%;
}
.image-list-box {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 16px 16px 0 16px;
}
.image-list-item {
width: 20vw;
height: 20vw;
}
.image-list-item-img {
width: 20vw;
height: 20vw;
border-radius: 12px;
border: 3px solid #fff;
background: rgba(0, 0, 0, 0.3);
}
.image-list-item-img.imgActive {
border: 3px solid #50cf54;
}
.image-list-item-loading {
width: 20vw;
height: 20vw;
border-radius: 12px;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #999;
}
.shape-body {
background: #fff;
border-radius: 8px;
padding: 0 20px;
}
.shape-box {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: flex-start;
}
.shape-title {
padding-bottom: 12px;
font-size: 14px;
font-weight: bold;
}
.shape-item {
position: relative;
flex: 0 0 24%;
/* margin-right: calc(4% / 3); */
margin-bottom: calc(4% / 2);
margin-right: 8px!important;
}
.shape-item:nth-child(4n){
margin-right: 0;
}
.shape-item:last-child{
margin-right: auto;
}
.shape-item-image {
width: 20vw;
height: 20vw;
border-radius: 8px;
}
.shape-icon {
position: absolute;
right: 6px;
bottom: 24px;
}
.shape-item-text {
font-size: 12px;
text-align: center;
}
.shape-type {
display: -webkit-box;
overflow-x: scroll;
scrollbar-width: none;
-ms-overflow-style: none;
padding-bottom: 16px;
}
.shape-type-item {
background: #F0F2F5;
font-size: 12px;
height: 7vw;
padding: 0px 16px;
border-radius: 4px;
margin-right: 8px;
display: flex;
justify-content: center;
align-items: center;
}
.shape-active {
background: #15CF5F;
color: #fff;
}
.shape-box-input {
display: flex;
align-items: center;
justify-content: center;
margin-top: 10px;
}
.shape-box-input-text {
width: 100%;
height: 40px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 0 10px;
}
.shape-text {
position: absolute;
z-index: 2;
font-family: chinese;
overflow: hidden;
text-shadow: 1px 0px 1px #000000a1;
white-space: pre-wrap;
}
.shape-item-image-round {
border-radius: 50%;
}
.van-address-edit {
padding: 0!important;
}
</style>