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.
 
 
 
 

1352 lines
34 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="">
<!-- <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-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">环保树脂</div>
<div class="info-content">材料</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">
<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 v-if="orderStat.use_type == 1">
<div class="order-item" v-show="item.shape_id == shapeId">
<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">
<div class="order-title" style="padding: 0 16px;">收货地址</div>
<div class="address-item">
<van-address-edit
:area-list="areaList"
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('')
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: group.value,
prod_id: prodId.value,
type_id: typeId.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 changeImage = (item: any) => {
imgKey.value = item.key
imageUrl.value = item.origin_url
imageWater.value = item.url
}
const createLog = () => {
badgeApi.createLog({
pid: pid.value,
group: group.value,
prod_id: prodId.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: group.value,
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 (orderStat.value.use_type == 1) {
sizeList.value.forEach((item: any) => {
if (item.shape_item) {
if (!shapeList.value.some(shape => shape.id === item.shape_id)) {
shapeList.value.push(item.shape_item)
}
} else {
showToast('该链接异常,请联系客服')
return
}
})
if (shapeList.value.length > 0) {
shapeChange(shapeList.value[0])
}
} else {
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 key = ref(0)
const loading = ref(false)
const confirm = () => {
if (loading.value) return
console.log('confirm')
if (payAmount.value <= 0) {
showToast('请先选择下单数量')
return
}
if (typeId.value == 2 && (shapeText.value == '' || !shapeText.value.trim())) {
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
}
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 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: group.value,
prod_id: prodId.value
}).then((res: any) => {
console.log('getImageList', res)
const data = res || []
flag.value = data.flag
prodId.value = data.prod_id
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) {
imageUrl.value = firstGeneratedImage.origin_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) {
imageUrl.value = currentImage.origin_url
imageWater.value = currentImage.url
isPreview.value = true
} else {
imageUrl.value = currentImage.origin_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) {
imageUrl.value = data.list.filter((item: any) => item.key == imgKey.value)[0].origin_url
imageWater.value = data.list.filter((item: any) => item.key == imgKey.value)[0].url
} else {
imageUrl.value = data.list[0].origin_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 typeId = ref(1)
// const typesList = ref([])
// const getTypesList = () => {
// badgeApi.getTypesList({}).then((res: any) => {
// console.log('getTypesList', res)
// typesList.value = res.list
// typeId.value = res.list[0].id
// getShapeList()
// }).catch((err) => {
// console.log('getTypesList', err)
// }).finally((err) => {
// })
// }
const shapeId = ref(1)
const shapeImage = ref('')
const shapeList = ref([])
const custom_switch = ref(0)
const limitCount = ref(10)
const getShapeList = () => {
badgeApi.getShapeList({
prod_id: prodId.value,
type_id: orderStat.value.type_id
}).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)
handleInput(e.target.value)
}
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 charBytes = char.charCodeAt(0) > 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);
// 中文字符和特殊符号占2字节,英文占1字节
len += code > 255 ? 2 : 1;
}
return len;
}
// const typeChange = (id: number) => {
// console.log('typeChange', id)
// typeId.value = id;
// shapeList.value = [];
// shapeImage.value = '';
// getStyle.value = '';
// getShapeList()
// }
const shapeChange = (item: any) => {
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
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
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)
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
}
// pid.value = 281505;
// group.value = 1;
// prodId.value = 7;
// getTypesList()
getImageList()
progressList()
getCompareImage()
getTrialCode()
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;
}
.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;
}
.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;
}
.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;
}
.shape-item-image-round {
border-radius: 50%;
}
.van-address-edit {
padding: 0!important;
}
</style>