forked from natuka/web.puabadge.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.
1554 lines
48 KiB
1554 lines
48 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" /> {{ toValueWithout("效果预览") }}</span> |
|
</div> |
|
<div class="step-container"> |
|
<div class="step-item"> |
|
<div class="step-content"> |
|
<div class="step-title">{{ toValueWithout("正面照片") }}</div> |
|
<div class="step-desc">{{ toValueWithout("清晰的正面照片") }}</div> |
|
</div> |
|
</div> |
|
<div class="step-item active"> |
|
<div class="step-num">2</div> |
|
<div class="step-content"> |
|
<div class="step-title">{{ toValueWithout("确认下单") }}</div> |
|
<div class="step-desc">{{ toValueWithout("选择一个你喜欢的效果图下单") }}</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">{{ toValueWithout(`总计大约需要${config.preview.progress.totalTime}秒,请耐心等待...`) }}</div> |
|
</div> |
|
<div v-else class="progress-section-picture" :style="`width: ${config.preview.imageSize.previewWidth}px;height: ${config.preview.imageSize.previewHeight}px;`"> |
|
<div class="progress-section-img" :style="`width: ${config.preview.imageSize.previewWidth}px;height: ${config.preview.imageSize.previewHeight}px;`"> |
|
<img v-if="shapeImage" class="box1-back-image" :src="getImageUrl(shapeImage)" :style="`width: ${config.preview.imageSize.previewWidth}px;height: ${config.preview.imageSize.previewHeight}px;`" alt=""> |
|
<div class="box1-front-box" :style="getStyle"> |
|
<img class="box1-front-image" :src="imageUrl" alt="" :style="`max-height: ${config.preview.imageSize.previewHeight}px;`"> |
|
</div> |
|
<div class="shape-text" :style="shapeTextStyle" v-if="shapeText">{{shapeText}}</div> |
|
<div class="shape-text" :style="topTextStyle" v-if="topText">{{topText}}</div> |
|
<div class="shape-text" :style="bottomTextStyle" v-if="bottomText">{{bottomText}}</div> |
|
<div v-if="badgeTypeId == 6 && axisz == 1 && roundTextConfigFront.width && roundTextConfigFront.charList" class="roundText font-chinese" :style="`left: ${roundTextConfigFront.x}px;top: ${roundTextConfigFront.y}px;width: ${roundTextConfigFront.width}px;height: ${roundTextConfigFront.height}px;position: absolute;pointer-events: none;z-index: 10;`"> |
|
<div v-for="item in roundTextConfigFront.charList" :key="item.char" class="round-text-char" :style="`position: absolute;left: ${item.x}px;top: ${item.y}px;font-size: ${item.fontSize}px;font-weight: ${item.fontWeight};color: ${item.color};transform: translate(-50%, -50%) rotate(${item.rotateDeg}deg);transform-origin: center;white-space: nowrap;`"> |
|
<span>{{item.char}}</span> |
|
</div> |
|
</div> |
|
<div v-if="badgeTypeId == 6 && axisz == -1 && roundTextConfigBack.width && roundTextConfigBack.charList" class="roundText font-chinese" :style="`left: ${roundTextConfigBack.x}px;top: ${roundTextConfigBack.y}px;width: ${roundTextConfigBack.width}px;height: ${roundTextConfigBack.height}px;position: absolute;pointer-events: none;z-index: 10;`"> |
|
<div v-for="(item, index) in roundTextConfigBack.charList" :key="index" class="round-text-char" :style="`position: absolute;left: ${item.x}px;top: ${item.y}px;font-size: ${item.fontSize}px;font-weight: ${item.fontWeight};color: ${item.color};transform: translate(-50%, -50%) rotate(${item.rotateDeg}deg);transform-origin: center;white-space: nowrap;`"> |
|
<span>{{item.char}}</span> |
|
</div> |
|
</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"> |
|
{{ toValueWithout(config.preview.messages.designing) }} |
|
</div> |
|
<div v-else-if="item.status == 2" class="image-list-item-loading"> |
|
{{ toValueWithout(config.preview.messages.designFailed) }} |
|
</div> |
|
</div> |
|
</div> |
|
<div class="info-section"> |
|
<div class="info-item"> |
|
<div class="info-title" v-if="productName">{{ toValueWithout(productName) }}</div> |
|
<div class="info-content">{{ toValueWithout(config.preview.messages.productType) }}</div> |
|
</div> |
|
<div class="info-item"> |
|
<div class="info-title">{{ toValueWithout("3D全彩打印") }}</div> |
|
<div class="info-content">{{ toValueWithout(config.preview.messages.process) }}</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': config.preview.shapeIds.round.includes(item.id) }" :src="getImageUrl(item.cover_path) || imageUrl"/> |
|
<van-icon v-if="item.id == shapeId" class="shape-icon" :color="config.styles.colors.success" name="checked" size="18px" /> |
|
</div> |
|
<div class="shape-item-text">{{ toValueWithout(item.name) }}</div> |
|
</div> |
|
</block> |
|
</div> |
|
<div class="shape-box-input" v-if="imageUrl && custom_switch == 1 && typeId == 2"> |
|
<div v-if="config.preview.shapeIds.topBottomText.includes(shapeId)"> |
|
<input class="shape-box-input-text" type="text" :placeholder="toValueWithout('请输入顶部文字')" v-model="topText" @change="changeTopText" @input="changeTopText" @onBlur="changeTopText" /> |
|
<input class="shape-box-input-text" type="text" :placeholder="toValueWithout('请输入底部文字')" style="margin-top: 10px;" v-model="bottomText" @change="changeBottomText" @input="changeBottomText" @onBlur="changeBottomText" /> |
|
</div> |
|
<div v-else style="width: 100%;"> |
|
<input class="shape-box-input-text" type="text" :placeholder="toValueWithout('请输入文字')" v-model="shapeText" @change="changeShapeText" @input="changeShapeText" @onBlur="changeShapeText" /> |
|
</div> |
|
</div> |
|
<div class="shape-box-input-box" v-if="imageUrl && custom_switch == 1 && typeId == 6"> |
|
<input class="shape-box-input-text" v-if="axisz == 1" type="text" :placeholder="toValueWithout('请输入正面文字')" v-model="frontText" @change="changeFrontText" @input="changeFrontText" @onBlur="changeFrontText" /> |
|
<input class="shape-box-input-text" v-if="axisz == -1" type="text" :placeholder="toValueWithout('请输入背面文字')" v-model="backText" @change="changeBackText" @input="changeBackText" @onBlur="changeBackText" /> |
|
</div> |
|
</div> |
|
<div style="padding: 0 16px;"> |
|
<van-divider /> |
|
</div> |
|
<div class="order-section" v-if="imageUrl"> |
|
<div class="order-title">{{ toValueWithout(config.preview.messages.orderQuantity) }}</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">({{ toValueWithout(config.preview.messages.remainingExchange) }}:{{ 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;">{{ toValueWithout(config.preview.messages.shippingAddress) }}</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="[toValueWithout('请选择'), toValueWithout('请选择'), toValueWithout('请选择')]" |
|
@change="nameTelChange" |
|
@change-area="changeArea" |
|
@change-detail="onChangeDetail" |
|
/> |
|
</div> |
|
</div> |
|
<div style="height: 130px;"></div> |
|
<div class="confirm-box"> |
|
<div class="action-section"> |
|
<!-- <button @click="sureReload" :disabled="flag < 1" class="action-btn"><img class="action-img" :src="reloadImage" alt=""> {{ toValueWithout(config.preview.messages.regenerate) }}</button> --> |
|
<button @click="compare" :disabled="flag < 1" class="action-btn"><img class="action-img" :src="compareImage" alt=""> {{ toValueWithout(config.preview.messages.compare) }}</button> |
|
<button @click="save" :disabled="flag < 1" class="action-btn"><img class="action-img" :src="downloadImage" alt=""> {{ toValueWithout(config.preview.messages.saveImage) }}</button> |
|
</div> |
|
<div class="btn-box"> |
|
<button @click="confirm" :disabled="flag < 1" class="confirm-btn">{{ toValueWithout(config.preview.messages.confirmSelect) }}</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<van-action-sheet v-model:show="showCompare" :title="toValueWithout(config.preview.messages.compare)" :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="toValueWithout(config.preview.messages.originalImage)" :style="`max-width: ${config.preview.imageSize.compareImageSize}px; max-height: ${config.preview.imageSize.compareImageSize}px; 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="toValueWithout(config.preview.messages.previewImage)" :style="`width: ${config.preview.imageSize.compareImageSize}px; height: ${config.preview.imageSize.compareImageSize}px; border-radius: 8px;`"> |
|
</div> |
|
</div> |
|
<div style="text-align: center; margin-top: 16px; color: #999; font-size: 12px;"> |
|
{{ toValueWithout(config.preview.messages.compareTip) }} |
|
</div> |
|
</div> |
|
</van-action-sheet> |
|
|
|
<div v-if="showPreview"> |
|
<div class="preview-mask" @click="showPreview = false"> |
|
<img :src="imageWater" :alt="toValueWithout(config.preview.messages.badgePreview)" /> |
|
<p>{{ toValueWithout(config.preview.messages.saveImageTip) }}</p> |
|
</div> |
|
</div> |
|
</template> |
|
<script setup lang="ts"> |
|
import { onMounted, ref, onUnmounted, nextTick, computed } from 'vue'; |
|
import { useRouter, useRoute } from 'vue-router'; |
|
import { showSuccessToast, showToast, showConfirmDialog } from 'vant'; |
|
import { localStorage } from '@/utils/local-storage' |
|
import * as badgeApi from '@/api/badge' |
|
import { areaList } from '@/utils/area' |
|
import { toValueWithout } from '@/lang/utils' |
|
import { createTextPosition } from '@/utils/textHelper' |
|
import { cartoonConfig as config } from '@/config/cartoon' |
|
|
|
// 导入图片资源 |
|
import reloadImageSrc from '@/assets/badge/reload.png' |
|
import compareImageSrc from '@/assets/badge/duibi.png' |
|
import downloadImageSrc from '@/assets/badge/down.png' |
|
|
|
const reloadImage = reloadImageSrc |
|
const compareImage = compareImageSrc |
|
const downloadImage = downloadImageSrc |
|
|
|
const router = useRouter(); |
|
const showCompare = ref(false) |
|
const imageList = ref([]) |
|
const imageWater = ref('') |
|
|
|
const frontText = ref('') |
|
const backText = ref('') |
|
const frontKey = ref(1) |
|
|
|
// 圆形文字配置 |
|
const roundTextConfigFront = ref({}) |
|
const roundTextConfigBack = ref({}) |
|
const cachedImageInfo = ref(null) |
|
const imageInfo = ref(null) |
|
const badgeTypeId = ref(0) |
|
const axisz = ref(1) |
|
|
|
// 创建适配 Vue 的 textHelper 实例 |
|
const textHelper = createTextPosition({ |
|
setData: (data: any) => { |
|
Object.keys(data).forEach(key => { |
|
if (key === 'roundTextConfigFront') { |
|
roundTextConfigFront.value = data[key] |
|
} else if (key === 'roundTextConfigBack') { |
|
roundTextConfigBack.value = data[key] |
|
} else if (key === 'cachedImageInfo') { |
|
cachedImageInfo.value = data[key] |
|
} else if (key === 'imageInfo') { |
|
imageInfo.value = data[key] |
|
} |
|
}) |
|
} |
|
}) |
|
|
|
// 计算产品名称 |
|
const productName = computed(() => { |
|
if (!typeId.value || !prodId.value) return '' |
|
const typeConfig = config.preview.productNames[typeId.value] |
|
if (typeConfig && typeConfig[prodId.value]) { |
|
return typeConfig[prodId.value] |
|
} |
|
return '' |
|
}) |
|
|
|
function compare() { |
|
showCompare.value = true |
|
} |
|
|
|
function sureReload() { |
|
console.log('orderStat', orderStat.value) |
|
if (orderStat.value.remain_count <= 0) { |
|
showToast(toValueWithout(config.preview.messages.noRemainingCount)) |
|
return |
|
} |
|
isPreview.value = false |
|
showConfirmDialog({ |
|
title: toValueWithout(config.preview.messages.regenerate), |
|
message: toValueWithout(config.preview.messages.confirmRegenerate), |
|
}) |
|
.then(() => { |
|
group.value = newGroup.value |
|
const params = { |
|
pid: pid.value, |
|
group: config.preview.defaults.group, |
|
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) |
|
showToast({ |
|
message: err.message, |
|
duration: 2000, |
|
}) |
|
}) |
|
}) |
|
.catch(() => { |
|
// on cancel |
|
}); |
|
} |
|
|
|
const imgKey = ref(config.preview.defaults.imgKey) |
|
const frameUrl = ref('') |
|
const changeImage = (item: any) => { |
|
console.log('changeImage', item) |
|
imgKey.value = item.key |
|
imageUrl.value = config.preview.shapeIds.specialFrame.includes(shapeId.value) ? item.frame_url : item.origin_url |
|
frameUrl.value = item.frame_url |
|
imageWater.value = item.image_url |
|
} |
|
|
|
const createLog = () => { |
|
badgeApi.createLog({ |
|
pid: pid.value, |
|
group: config.preview.defaults.group, |
|
prod_id: prodId.value, |
|
type_id: typeId.value, |
|
kind_id: kindId.value |
|
}).then((res: any) => { |
|
console.log('createLog', res) |
|
progressTimer.value = null |
|
getImageList() |
|
progressList() |
|
getCompareImage() |
|
timer.value = setInterval(() => { |
|
getImageList() |
|
}, config.preview.polling.interval) |
|
}).catch((err) => { |
|
console.log('createLog', err) |
|
showToast({ |
|
message: err.message, |
|
duration: 2000, |
|
}) |
|
}) |
|
} |
|
|
|
const showPreview = ref(false) |
|
function save() { |
|
showPreview.value = true; |
|
} |
|
const compareList = ref({}) |
|
const style_name = ref('') |
|
function getCompareImage() { |
|
badgeApi.getCompareImage({ |
|
pid: pid.value, |
|
group: config.preview.defaults.group, |
|
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, |
|
}) |
|
}) |
|
} |
|
function goBack() { |
|
router.back() |
|
} |
|
|
|
const sizeList = 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 loading = ref(false) |
|
const confirm = () => { |
|
if (loading.value) return |
|
console.log('confirm') |
|
if (payAmount.value <= 0) { |
|
showToast(toValueWithout(config.preview.messages.selectQuantity)) |
|
return |
|
} |
|
if (orderStat.value.use_type == 2 && !contact_name.value) { |
|
showToast(toValueWithout(config.preview.messages.fillContactName)) |
|
return |
|
} |
|
if (orderStat.value.use_type == 2 && !contact_mobile.value) { |
|
showToast(toValueWithout(config.preview.messages.fillContactMobile)) |
|
return |
|
} |
|
if (orderStat.value.use_type == 2 && !province_id.value) { |
|
showToast(toValueWithout(config.preview.messages.selectArea)) |
|
return |
|
} |
|
if (orderStat.value.use_type == 2 && !address.value) { |
|
showToast(toValueWithout(config.preview.messages.fillAddress)) |
|
return |
|
} |
|
getPosition() |
|
loading.value = true |
|
showConfirmDialog({ |
|
title: toValueWithout(config.preview.messages.confirmSelect), |
|
message: toValueWithout(config.preview.messages.confirmOrder), |
|
}) |
|
.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 ? shapeText.value : frontText.value, |
|
back_text: backText.value ? backText.value : '', |
|
shape_ids: shapeIds.value, |
|
type_id: typeId.value, |
|
up_text: topText.value ? topText.value : '', |
|
down_text: bottomText.value ? bottomText.value : '', |
|
subject_id: subjectId.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 = config.preview.defaults.addrId |
|
parms.country_id = config.preview.defaults.countryId |
|
} |
|
console.log('parms', parms) |
|
badgeApi.creatOrder(parms).then((res: any) => { |
|
console.log('creatOrder', res) |
|
showSuccessToast({ |
|
message: toValueWithout(config.preview.messages.orderSuccess), |
|
duration: 2000, |
|
}) |
|
router.push({ |
|
path: config.routes.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 |
|
}); |
|
} |
|
|
|
const getPosition = () => { |
|
badgeApi.composite({ |
|
pid: pid.value, |
|
num: imgKey.value, |
|
custom_text: shapeText.value ? shapeText.value : frontSaveText.value, |
|
back_text: backSaveText.value ? backSaveText.value : '', |
|
shape_id: shapeId.value, |
|
type_id: typeId.value, |
|
shape_ids: shapeIds.value, |
|
up_text: topText.value ? topText.value : '', |
|
down_text: bottomText.value ? bottomText.value : '', |
|
}).then((res: any) => { |
|
console.log('getPosition', res) |
|
}).catch((err) => { |
|
console.log('getPosition', err) |
|
}) |
|
} |
|
|
|
const imageUrl = ref('') |
|
const originUrl = ref('') |
|
const group = ref(config.preview.defaults.group); |
|
// 轮询获取图片 |
|
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 < config.preview.progress.maxProgress) { |
|
progress.value += 1 |
|
const thresholds = config.preview.progress.thresholds |
|
if (progress.value < thresholds.stage1) { |
|
progressText.value = toValueWithout(config.preview.progress.stages.stage1) |
|
} else if (progress.value < thresholds.stage2) { |
|
progressText.value = toValueWithout(config.preview.progress.stages.stage2) |
|
} else if (progress.value < thresholds.stage3) { |
|
progressText.value = toValueWithout(config.preview.progress.stages.stage3) |
|
} else { |
|
progressText.value = toValueWithout(config.preview.progress.stages.stage4) |
|
} |
|
} |
|
}, config.preview.progress.interval) |
|
} |
|
} |
|
|
|
const newGroup = ref(0) |
|
const isPreview = ref(false) |
|
const getImageList = () => { |
|
badgeApi.getImageList({ |
|
pid: pid.value, |
|
group: config.preview.defaults.group, |
|
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 |
|
})) |
|
|
|
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.image_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.image_url |
|
isPreview.value = true |
|
} else { |
|
originUrl.value = currentImage.origin_url |
|
imageUrl.value = currentImage.origin_url |
|
frameUrl.value = currentImage.frame_url |
|
imageWater.value = currentImage.image_url |
|
isPreview.value = true |
|
} |
|
} |
|
}) |
|
} |
|
if (data.flag === 2) { |
|
newGroup.value = data.next_group |
|
clearInterval(timer.value) |
|
clearInterval(progressTimer.value) |
|
|
|
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].image_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].image_url |
|
imgKey.value = data.list[0].key |
|
} |
|
localStorage.remove('userId') |
|
} |
|
}).catch((err) => { |
|
showToast({ |
|
message: err.message, |
|
duration: 2000, |
|
}) |
|
}) |
|
} |
|
|
|
const shapeId = ref(0) |
|
const shapeIds = ref([]) |
|
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 && typeId.value == 2) { |
|
shapeText.value = '' |
|
topText.value = '' |
|
bottomText.value = '' |
|
topTextStyle.value = '' |
|
bottomTextStyle.value = '' |
|
shapeTextStyle.value = '' |
|
textShow(res.list[0]) |
|
} |
|
if (custom_switch.value == 1 && typeId.value == 6) { |
|
shapeIds.value = res.list.map((item: any) => item.id) |
|
if (res.list[0].axisz == 1) { |
|
axisz.value = 1 |
|
frontKey.value = 1 |
|
frontTextShow(res.list[0], true) |
|
} else if (res.list[0].axisz == -1) { |
|
axisz.value = -1 |
|
frontKey.value = 2 |
|
frontTextShow(res.list[0], false) |
|
} else { |
|
axisz.value = 1 |
|
frontKey.value = 1 |
|
frontTextShow(res.list[0], true) |
|
} |
|
} |
|
}).catch((err) => { |
|
console.log('getShapeList', err) |
|
}) |
|
} |
|
|
|
const getImageUrl = (path: string) => { |
|
return 'https://suwa3d-3dview.oss-cn-shanghai.aliyuncs.com/' + path |
|
} |
|
|
|
const getStyle = ref('') |
|
const ImageShow = (item: any) => { |
|
const scale = config.preview.imageScale; |
|
const img = new Image() |
|
img.src = getImageUrl(item.frame_path) |
|
img.onload = () => { |
|
console.log('img', img) |
|
const ratioWidth = config.preview.imageSize.previewWidth / img.width; |
|
const ratioHeight = config.preview.imageSize.previewHeight / 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 topTextStyle = ref('') |
|
const bottomTextStyle = ref('') |
|
const textShow = (item: any, type: string = 'shape') => { |
|
const scale = config.preview.imageScale; |
|
const img = new Image() |
|
img.src = getImageUrl(item.frame_path) |
|
img.onload = () => { |
|
console.log('img', img) |
|
const ratioWidth = config.preview.imageSize.previewWidth / img.width; |
|
const ratioHeight = config.preview.imageSize.previewHeight / 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); |
|
if (type == 'shape') { |
|
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};` |
|
} else if (type == 'top') { |
|
topTextStyle.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};` |
|
} else if (type == 'bottom') { |
|
bottomTextStyle.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 frontTextShow = (event: any, isFront: boolean | null = null) => { |
|
if (isFront === null) { |
|
isFront = frontKey.value == 1; |
|
} |
|
isFront = Boolean(isFront); |
|
|
|
let config_text: any = {}; |
|
if (isFront) { |
|
config_text = { |
|
x: event.front_text_axisx || config.preview.textConfig.front.x, |
|
y: event.front_text_axisy || config.preview.textConfig.front.y, |
|
width: event.front_text_width || config.preview.textConfig.front.width, |
|
height: event.front_text_height || config.preview.textConfig.front.height, |
|
radius: event.front_text_radius || config.preview.textConfig.front.radius, |
|
fontSize: event.front_font_size || config.preview.textConfig.front.fontSize, |
|
maxLength: config.preview.textConfig.front.maxLength |
|
}; |
|
} else { |
|
config_text = { |
|
x: event.back_text_axisx !== undefined ? event.back_text_axisx : config.preview.textConfig.back.x, |
|
y: event.back_text_axisy !== undefined ? event.back_text_axisy : config.preview.textConfig.back.y, |
|
width: event.back_text_width !== undefined ? event.back_text_width : config.preview.textConfig.back.width, |
|
height: event.back_text_height !== undefined ? event.back_text_height : config.preview.textConfig.back.height, |
|
radius: event.back_text_radius !== undefined ? event.back_text_radius : config.preview.textConfig.back.radius, |
|
fontSize: event.back_font_size !== undefined ? event.back_font_size : config.preview.textConfig.back.fontSize, |
|
maxLength: config.preview.textConfig.back.maxLength |
|
}; |
|
} |
|
|
|
const text = isFront ? frontText.value : backText.value; |
|
const bindKey = isFront ? 'roundTextConfigFront' : 'roundTextConfigBack'; |
|
const shapeImagePath = event.frame_path || shapeImage.value; |
|
const cachedInfo = cachedImageInfo.value; |
|
const needFetchImage = !cachedInfo || cachedInfo.src !== shapeImagePath; |
|
|
|
if (!needFetchImage && cachedInfo) { |
|
textHelper.roundText({ |
|
shapeImage: shapeImagePath, |
|
x: config_text.x, |
|
y: config_text.y, |
|
width: config_text.width, |
|
height: config_text.height, |
|
radius: config_text.radius, |
|
text: text, |
|
isFront: isFront, |
|
style: { |
|
fontSize: config_text.fontSize, |
|
color: event.font_color || config.preview.textConfig.defaultColor, |
|
fontWeight: event.font_weight || config.preview.textConfig.defaultWeight, |
|
textAlign: 'center', |
|
}, |
|
bindKey: bindKey, |
|
cachedImageInfo: cachedInfo |
|
}); |
|
|
|
nextTick(() => { |
|
const currentConfig = isFront ? roundTextConfigFront.value : roundTextConfigBack.value; |
|
if (frontKey.value == (isFront ? 1 : 2) && currentConfig && currentConfig.charList) { |
|
// Vue 会自动响应式更新 |
|
} |
|
}); |
|
} else { |
|
textHelper.roundText({ |
|
shapeImage: shapeImagePath, |
|
x: config_text.x, |
|
y: config_text.y, |
|
width: config_text.width, |
|
height: config_text.height, |
|
radius: config_text.radius, |
|
text: text, |
|
isFront: isFront, |
|
style: { |
|
fontSize: config_text.fontSize, |
|
color: event.font_color || config.preview.textConfig.defaultColor, |
|
fontWeight: event.font_weight || config.preview.textConfig.defaultWeight, |
|
textAlign: 'center', |
|
}, |
|
bindKey: bindKey, |
|
onImageInfoCached: (imageInfo: any) => { |
|
cachedImageInfo.value = imageInfo; |
|
} |
|
}); |
|
|
|
const checkAndUpdate = (attempts = 0) => { |
|
if (attempts > 10) return; |
|
|
|
const currentConfig = isFront ? roundTextConfigFront.value : roundTextConfigBack.value; |
|
if (frontKey.value == (isFront ? 1 : 2)) { |
|
if (currentConfig && currentConfig.charList && currentConfig.charList.length > 0) { |
|
// Vue 会自动响应式更新 |
|
} else if (attempts < 10) { |
|
setTimeout(() => { |
|
checkAndUpdate(attempts + 1); |
|
}, 50); |
|
} |
|
} |
|
}; |
|
|
|
setTimeout(() => { |
|
checkAndUpdate(0); |
|
}, 50); |
|
} |
|
} |
|
|
|
const shapeText = ref('') |
|
const changeShapeText = (e: any) => { |
|
console.log('changeShapeText', e.target.value) |
|
if (!validateInput(e.target.value)) { |
|
showToast(config.preview.messages.invalidInput) |
|
shapeText.value = e.target.value.replace(/[^a-zA-Z0-9\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7a3\u00c0-\u017f\s~!@#¥%……&*()——+_)(*&^%$#@!~?><:"}{|、】【';、。,'.]/g, ''); |
|
return; |
|
} |
|
handleInput(e.target.value) |
|
} |
|
|
|
const topText = ref('') |
|
const changeTopText = (e: any) => { |
|
console.log('changeTopText', e.target.value) |
|
if (!validateInput(e.target.value)) { |
|
showToast(config.preview.messages.invalidInput) |
|
topText.value = e.target.value.replace(/[^a-zA-Z0-9\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7a3\u00c0-\u017f\s~!@#¥%……&*()——+_)(*&^%$#@!~?><:"}{|、】【';、。,'.]/g, ''); |
|
return; |
|
} |
|
handleInput(e.target.value, 'top') |
|
const textPos = shapeList.value.find((item: any) => item.id === shapeId.value)?.text_pos?.find((d: any) => d.position_place == 'top') |
|
if (textPos) { |
|
textPos.frame_path = shapeImage.value; |
|
textShow(textPos, 'top') |
|
} |
|
} |
|
|
|
const bottomText = ref('') |
|
const changeBottomText = (e: any) => { |
|
console.log('changeBottomText', e.target.value) |
|
if (!validateInput(e.target.value)) { |
|
showToast(config.preview.messages.invalidInput) |
|
bottomText.value = e.target.value.replace(/[^a-zA-Z0-9\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7a3\u00c0-\u017f\s~!@#¥%……&*()——+_)(*&^%$#@!~?><:"}{|、】【';、。,'.]/g, ''); |
|
return; |
|
} |
|
handleInput(e.target.value, 'bottom') |
|
const textPos = shapeList.value.find((item: any) => item.id === shapeId.value)?.text_pos?.find((d: any) => d.position_place == 'bottom') |
|
if (textPos) { |
|
textPos.frame_path = shapeImage.value; |
|
textShow(textPos, 'bottom') |
|
} |
|
} |
|
|
|
const frontSaveText = ref('') |
|
const changeFrontText = (e: any) => { |
|
const inputValue = e?.target?.value ?? frontText.value |
|
console.log('changeFrontText', inputValue) |
|
frontText.value = inputValue |
|
frontSaveText.value = inputValue |
|
handleInput(inputValue) |
|
if (typeId.value == 6 && shapeList.value.length > 0) { |
|
const currentShape = shapeList.value.find((item: any) => item.id === shapeId.value) |
|
if (currentShape && currentShape.axisz == 1) { |
|
frontTextShow(currentShape, true) |
|
} |
|
} |
|
} |
|
|
|
const backSaveText = ref('') |
|
const changeBackText = (e: any) => { |
|
const inputValue = e?.target?.value ?? backText.value |
|
console.log('changeBackText', inputValue) |
|
backText.value = inputValue |
|
backSaveText.value = inputValue |
|
handleInput(inputValue) |
|
if (typeId.value == 6 && shapeList.value.length > 0) { |
|
const currentShape = shapeList.value.find((item: any) => item.id === shapeId.value) |
|
if (currentShape && currentShape.axisz == -1) { |
|
frontTextShow(currentShape, false) |
|
} |
|
} |
|
} |
|
|
|
const validateInput = (input: string) => { |
|
const regex = /^[a-zA-Z0-9\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7a3\u00c0-\u017f\s~!@#¥%……&*()——+_)(*&^%$#@!~?><:"}{|、】【';、。,'.]+$/; |
|
return regex.test(input); |
|
} |
|
|
|
const handleInput = (value: string, type: string = 'shape') => { |
|
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; |
|
} |
|
} |
|
if (typeId.value == 2) { |
|
if (config.preview.shapeIds.topBottomText.includes(shapeId.value)) { |
|
type == 'top' ? topText.value = validValue : bottomText.value = validValue; |
|
} else { |
|
shapeText.value = validValue; |
|
} |
|
} else { |
|
if (axisz.value == 1) { |
|
frontText.value = validValue; |
|
frontSaveText.value = validValue; |
|
} else { |
|
backText.value = validValue; |
|
backSaveText.value = validValue; |
|
} |
|
} |
|
if (typeId.value == 6 && shapeList.value.length > 0) { |
|
const currentShape = shapeList.value.find((item: any) => item.id === shapeId.value) |
|
if (currentShape) { |
|
if (currentShape.axisz == 1) { |
|
frontTextShow(currentShape, true) |
|
} else if (currentShape.axisz == -1) { |
|
frontTextShow(currentShape, false) |
|
} |
|
} |
|
} |
|
return; |
|
} |
|
if (typeId.value == 2) { |
|
if (config.preview.shapeIds.topBottomText.includes(shapeId.value)) { |
|
type == 'top' ? topText.value = value : bottomText.value = value; |
|
} else { |
|
shapeText.value = value; |
|
} |
|
} else { |
|
if (axisz.value == 1) { |
|
frontText.value = value; |
|
frontSaveText.value = value; |
|
} else { |
|
backText.value = value; |
|
backSaveText.value = value; |
|
} |
|
} |
|
if (typeId.value == 6 && shapeList.value.length > 0) { |
|
const currentShape = shapeList.value.find((item: any) => item.id === shapeId.value) |
|
if (currentShape) { |
|
if (currentShape.axisz == 1) { |
|
frontTextShow(currentShape, true) |
|
} else if (currentShape.axisz == -1) { |
|
frontTextShow(currentShape, false) |
|
} |
|
} |
|
} |
|
} |
|
|
|
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 = config.preview.shapeIds.frame.includes(item.id) ? originUrl.value : frameUrl.value |
|
} else { |
|
imageUrl.value = originUrl.value |
|
} |
|
ImageShow(item) |
|
if (custom_switch.value == 1 && typeId.value == 2) { |
|
shapeText.value = '' |
|
topText.value = '' |
|
bottomText.value = '' |
|
topTextStyle.value = '' |
|
bottomTextStyle.value = '' |
|
shapeTextStyle.value = '' |
|
textShow(item) |
|
} |
|
if (custom_switch.value == 1 && typeId.value == 6) { |
|
if (item.axisz == 1) { |
|
axisz.value = 1 |
|
frontKey.value = 1 |
|
frontText.value = frontSaveText.value ? frontSaveText.value : '' |
|
backText.value = backSaveText.value ? backSaveText.value : '' |
|
frontTextShow(item, true) |
|
} else if (item.axisz == -1) { |
|
axisz.value = -1 |
|
frontKey.value = 2 |
|
backText.value = backSaveText.value ? backSaveText.value : '' |
|
frontText.value = frontSaveText.value ? frontSaveText.value : '' |
|
frontTextShow(item, false) |
|
} |
|
} |
|
} |
|
|
|
const orderStat = ref({}) |
|
const typeId = ref(0) |
|
const subjectId = ref(0) |
|
const getOrderStat = () => { |
|
badgeApi.getOrderStat({}).then((res: any) => { |
|
console.log('getOrderStat', res) |
|
orderStat.value = res |
|
typeId.value = res.type_id |
|
subjectId.value = res.subject_id |
|
badgeTypeId.value = res.type_id |
|
getSizeList() |
|
}) |
|
} |
|
|
|
function handleBeforeUnload(_event: BeforeUnloadEvent) { |
|
// code 的清除已在 main.ts 中统一处理,这里只处理页面特定的清理逻辑 |
|
// 清除轮询和进度条计时器 |
|
clearInterval(timer.value) |
|
clearInterval(progressTimer.value) |
|
// userId 的清除由 main.ts 统一处理 |
|
} |
|
|
|
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) |
|
} |
|
getOrderStat() |
|
|
|
getImageList() |
|
progressList() |
|
setTimeout(() => { |
|
getCompareImage() |
|
}, config.preview.polling.compareImageDelay) |
|
timer.value = setInterval(() => { |
|
getImageList() |
|
}, config.preview.polling.interval) |
|
window.addEventListener('beforeunload', handleBeforeUnload); |
|
}) |
|
|
|
onUnmounted(() => { |
|
clearInterval(timer.value) |
|
clearInterval(progressTimer.value) |
|
}) |
|
|
|
const searchResult = ref([]) |
|
</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-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-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); |
|
} |
|
.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; |
|
} |
|
.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; |
|
} |
|
.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%); |
|
} |
|
.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: flex-start; |
|
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; |
|
top: 14vw; |
|
} |
|
.shape-item-text { |
|
font-size: 12px; |
|
text-align: center; |
|
max-width: 20vw; |
|
} |
|
.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-box { |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
margin-top: 10px; |
|
gap: 10px; |
|
flex-direction: column; |
|
} |
|
.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; |
|
} |
|
.font-chinese { |
|
font-family: chinese; |
|
} |
|
.shape-item-image-round { |
|
border-radius: 50%; |
|
} |
|
.van-address-edit { |
|
padding: 0!important; |
|
} |
|
</style> |