Files
ece.suwa3d.com/src/views/badge/index.vue
T
2026-01-16 10:48:19 +08:00

1474 lines
38 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="photo-upload-page">
<div class="badge-size" v-if="trialCode == false">
<div class="order-type">
<div class="order-type-item">
<div class="order-type-item-title" v-if="orderStat.order_no">
{{ toValueWithout("订单编号") }}
</div>
<div class="order-type-item-title">
{{ orderStat.order_no }}
</div>
</div>
<div class="order-type-item" v-if="prodId">
<div class="order-type-item-title">
{{ toValueWithout("产品类型") }}
</div>
<div class="order-type-item-title" v-if="typeId == 1">
{{ toValueWithout("立体徽章") }}
</div>
<div class="order-type-item-title" v-if="typeId == 2">
{{ toValueWithout("浮雕相框") }}
</div>
<div class="order-type-item-title" v-if="typeId == 3">
{{ toValueWithout("3D冰箱贴") }}
</div>
<div class="order-type-item-title" v-if="typeId == 4">
{{ toValueWithout("3D卡通手办") }}
</div>
<div class="order-type-item-title" v-if="typeId == 6">
{{ toValueWithout("浮雕挂饰") }}
</div>
</div>
</div>
<div class="size-title">
{{ toValueWithout("剩余兑换数量") }}
</div>
<div class="size-options">
<div class="size-item" v-for="item in sizeList" :key="item.id">
<div class="size-text">
{{ item.size }}
</div>
<div class="size-count">
({{ toValueWithout("剩余兑换") }}:{{ item.remaining }})
</div>
</div>
</div>
</div>
<div class="badge-info" v-if="trialCode == false">
<div class="badge-item" @click="goToRecord">
<div class="badge-title">
{{ toValueWithout("设计图集") }}
</div>
<div class="badge-count">
{{ orderStat.create_count || 0 }}{{ toValueWithout("张") }}
</div>
</div>
<div class="badge-item" @click="goToMyOrder">
<div class="badge-title">
{{ toValueWithout("我的订单") }}
</div>
<div class="badge-count">
{{ orderStat.order_count || 0 }}{{ toValueWithout("笔") }}
</div>
</div>
</div>
<div class="badge-phone" v-if="trialCode == true">
<van-field
:label="toValueWithout('手机号码')"
type="number"
v-model="mobile"
:placeholder="toValueWithout('请输入手机号')"
required
maxlength="11"
:rules="[{ required: true, message: toValueWithout('请输入手机号') }]"
@blur="onBlur"
@confirm="onBlur"
/>
<p style="font-size: 12px; color: #000; margin-left: 16px;">{{ toValueWithout("注:需要先输入手机号才可体验徽章设计") }}</p>
<div class="style-box">
<div class="style-box-item">
<div class="style-item-title" :class="{ styleActive: prodId == 7 || prodId == 1 || prodId == 25 }" @click="styleChange(7)">
{{ prop == '3D真人肖像' ? toValueWithout('3D真人肖像') : toValueWithout('人物主体') }}
</div>
<div class="style-item-title" :class="{ styleActive: prodId == 8 || prodId == 2 || prodId == 24 }" @click="styleChange(8)">
{{ toValueWithout("宠物主体") }}
</div>
</div>
</div>
</div>
<div style="height: 8px;background: #F2F2F2;"></div>
<div class="step-container" v-if="trialCode == false">
<div class="step-item active">
<div class="step-num">
1
</div>
<div class="step-content">
<div class="step-title">
{{ toValueWithout("上传正面照片") }}
</div>
<div class="step-desc">
{{ toValueWithout("1张五官清晰的正面照片") }}
</div>
</div>
</div>
<div class="step-item">
<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 class="tag-change-box" v-if="trialCode == false">
<div class="tag-change-item">
<div class="tag-change-item-title" :class="{ tagActive: prodId == 7 || prodId == 1 || prodId == 25 }" @click="prodChange(7)">
{{ toValueWithout("人物主体") }}
</div>
<div class="tag-change-item-title" :class="{ tagActive: prodId == 8 || prodId == 19 || prodId == 24 }" @click="prodChange(8)">
{{ toValueWithout("宠物主体") }}
</div>
</div>
<div style="font-size: 12px; color: #888; text-align: center;">{{ toValueWithout("不同主体的照片检测标准不同") }}</div>
</div>
<div class="step-line" v-if="prop == '3D真人肖像'">
<div class="step-line-item">
<div class="step-line-item-title">
<span style="display: flex; align-items: center;justify-content: center;">
<span style="height: 1px; background: #ccc;width: 50px;"></span>
<span style="margin: 0 8px; color: #888;">{{ toValueWithout("照片示例") }}</span>
<span style="height: 1px; background: #ccc;width: 50px;"></span>
</span>
</div>
<div class="step-line-item-desc" style="display: flex; align-items: center;justify-content: space-around;padding: 16px 16px 0 16px;">
<div class="step-line-item-desc-item">
<img src="@/assets/badge/1.png" style="width: 20vw;height: 20vw;border-radius: 8px;" alt="">
</div>
<div class="step-line-item-desc-item">
<img src="@/assets/badge/2.png" style="width: 20vw;height: 20vw;border-radius: 8px;" alt="">
</div>
<div class="step-line-item-desc-item">
<img src="@/assets/badge/3.png" style="width: 20vw;height: 20vw;border-radius: 8px;" alt="">
</div>
<div class="step-line-item-desc-item">
<img src="@/assets/badge/4.png" style="width: 20vw;height: 20vw;border-radius: 8px;" alt="">
</div>
</div>
</div>
</div>
<div class="photo-upload-body">
<div v-if="!picture" class="photo-upload-box">
<div class="photo-upload-header" @click="imgShow = true">
<van-icon name="question-o" size="16px" class="photo-upload-guide-icon" />
<span class="photo-upload-guide-text">{{ toValueWithout("图片上传指南") }}</span>
</div>
<div class="photo-upload-area">
<div class="photo-upload-plus">
+
</div>
<div class="photo-upload-text">
{{ toValueWithout("点击上传照片") }}
</div>
<div class="photo-upload-tip">
*{{ toValueWithout("上传照片时建议勾选[原图]") }}
</div>
</div>
<div class="photo-upload-footer">
{{ toValueWithout("请确定您对上传的照片拥有合法使用权利或已取得他人合法授权,且同意本平台分析图片信息以提供生成服务") }}
</div>
</div>
<img class="photo-upload-img" v-if="picture" :src="picture" alt="" srcset="">
<div class="photo-upload-box-1">
<h5-cropper :option="option" @getbase64Data="getbase64Data" @imgorigoinf="imgorigoinf"></h5-cropper>
</div>
</div>
<div v-if="typeId == 4 && kindList.length > 0">
<div style="font-size: 16px; color: #222; font-weight: bold; margin-top: 16px;padding: 0 16px;">{{ toValueWithout("风格类型") }}</div>
<div class="kind-box">
<div class="kind-box-item" v-for="item in kindList" :key="item.id" :class="{ styleActive: kindId == item.id }">
<div class="kind-item-title" @click="kindChange(item.id)">
{{ toValueWithout(item.name) }}
</div>
</div>
</div>
</div>
<div v-if="typeId == 4 && prodId == 19">
<div style="font-size: 16px; color: #222; font-weight: bold; margin-top: 16px;padding: 0 16px;">{{ toValueWithout("多视角参考图") }}</div>
<div style="font-size: 12px; color: #aaa; margin-top: 8px;padding: 0 16px;">{{ toValueWithout("AI建模具有随机性,上传参考图有助于更好的还原主体特征") }}</div>
<div class="photo-upload-box-refer">
<van-uploader v-model="referPicture" multiple :max-count="3" :max-size="10 * 1024 * 1024" @oversize="onOversize" :deletable="true" :show-preview="true" :before-read="beforeRead" upload-icon="plus" />
</div>
</div>
<div v-if="typeId != 4">
<div style="font-size: 12px; color: red; margin-top: 16px; text-align: center;" v-if="prodId == 7 || prodId == 1 || prodId == 25">{{ toValueWithout("温馨提示:请上传只有") }}{{ prop == '3D真人肖像' ? '1' : '1-3' }}{{ toValueWithout("人的照片") }}</div>
<div style="font-size: 12px; color: red; margin-top: 16px; text-align: center;" v-if="prodId == 8 || prodId == 2 || prodId == 24">{{ toValueWithout("温馨提示:请上传只有") }}1-3{{ toValueWithout("只宠物的照片") }}</div>
</div>
<div style="height: 120px;"></div>
<div class="design-action-bar">
<div class="design-left">
<img class="design-leaf-icon" width="18" height="18" src="@/assets/badge/leaf.png" alt="">
<span v-if="trialCode == false" class="design-remaining">{{ toValueWithout("剩余") }}{{ orderStat.remain_count || 0 }}{{ toValueWithout("次") }}</span>
<span v-if="trialCode == true" class="design-remaining">{{ toValueWithout("体验1次") }}</span>
</div>
<button class="design-btn" @click="goToPreview">
<span>{{ toValueWithout("开始设计") }}</span>
<img class="design-arrow" src="@/assets/badge/arrow.png" alt="">
</button>
</div>
</div>
<van-action-sheet v-model:show="imgShow" :title="toValueWithout('图片上传指南')" :close-on-click-overlay="true" closeable>
<div style="padding: 0 16px 24px 16px;">
<div style="text-align: center;">
<img :src="sundryList[0].path" alt="照片上传指南" style="width: 90vw; border-radius: 16px; margin-bottom: 16px;">
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 16px;">
<div style="flex: 1; text-align: center;">
<img :src="sundryList[1].path" alt="多主体" style="width: 25vw; height: 25vw; border-radius: 8px; object-fit: cover;">
<div style="font-size: 13px; color: #888; margin-top: 4px;">
{{ toValueWithout("多主体") }}
</div>
</div>
<div style="flex: 1; text-align: center;">
<img :src="sundryList[2].path" alt="光线昏暗" style="width: 25vw; height: 25vw; border-radius: 8px; object-fit: cover;">
<div style="font-size: 13px; color: #888; margin-top: 4px;">
{{ toValueWithout("光线昏暗") }}
</div>
</div>
<div style="flex: 1; text-align: center;">
<img :src="sundryList[3].path" alt="后侧面及背影" style="width: 25vw; height: 25vw; border-radius: 8px; object-fit: cover;">
<div style="font-size: 13px; color: #888; margin-top: 4px;">
{{ toValueWithout("后侧面及背影") }}
</div>
</div>
</div>
<div style="display: flex; justify-content: space-around; margin-bottom: 24px;">
<div style="flex: 1; text-align: center;">
<img :src="sundryList[4].path" alt="照片模糊" style="width: 25vw; height: 25vw; border-radius: 8px; object-fit: cover;">
<div style="font-size: 13px; color: #888; margin-top: 4px;">
{{ toValueWithout("照片模糊") }}
</div>
</div>
<div style="flex: 1; text-align: center;">
<img :src="sundryList[5].path" alt="照片留白过少" style="width: 25vw; height: 25vw; border-radius: 8px; object-fit: cover;">
<div style="font-size: 13px; color: #888; margin-top: 4px;">
{{ toValueWithout("照片留白过少") }}
</div>
</div>
</div>
<div style="margin-top: 24px; text-align: center;">
<button style="width: 90%; height: 44px; background: linear-gradient(90deg, #d6f5b7 0%, #50cf54 100%); border: none; border-radius: 22px; color: #222; font-size: 18px; font-weight: bold; cursor: pointer;" @click="imgShow = false">
{{ toValueWithout("我已了解") }}
</button>
</div>
</div>
</van-action-sheet>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import H5Cropper from 'vue-cropper-h5'
import "vue-cropper-h5/dist/style.css"
import { showLoadingToast, showToast, closeToast, showFailToast, showSuccessToast } from 'vant';
import type { UploaderFileListItem } from 'vant'
import * as badgeApi from '@/api/badge'
import { useTranslation } from "i18next-vue";
import * as i18n from '@/lang/utils'
import { toValueWithout } from '@/lang/utils'
import { localStorage } from '@/utils/local-storage'
import CryptoJS from 'crypto-js'
// 初始化翻译功能
const { t } = useTranslation();
i18n.set(t);
const show = ref(false)
const imgShow = ref(false)
const referPicture = ref<UploaderFileListItem[]>([])
const router = useRouter()
const option = ref({
canScale: true,
autoCropWidth: 1024,
autoCropHeight: 1024,
ceilbutton: true,
infoTrue: true,
fixed: false,
fixedNumber: [3, 4],
fixedBox: false,
canMoveBox: true,
enlarge: 2
})
const options = ref({
autoCropWidth: 1000,
autoCropHeight: 1000,
ceilbutton: false,
info: true
})
onMounted(() => {
watch(
() => prodId.value,
() => {
getSundryList()
}
)
})
// 判断是否是APP
const openApp = () => {
const ua = navigator.userAgent;
const isIOS = /iPhone|iPad|iPod/i.test(ua);
const isAndroid = /Android/i.test(ua);
if (isIOS) {
option.value.ceilbutton = true
options.value.ceilbutton = true
} else if (isAndroid) {
option.value.ceilbutton = false
options.value.ceilbutton = false
} else {
option.value.ceilbutton = true
options.value.ceilbutton = true
}
}
const imgurl = ref('')
const imgBgUrl = ref('')
const remaining = ref(0)
// 跳转设计图集
function goToRecord() {
router.push('/badge/record')
}
// 跳转我的订单
function goToMyOrder() {
router.push('/badge/myOrder')
}
// 获取兑换数量
const sizeList = ref([])
const getSizeList = () => {
badgeApi.getOrderPrice({}).then((res: any) => {
sizeList.value = res
})
}
// 获取首页显示的数量和类型
const orderStat = ref({})
const prodId = ref(7)
const prop = ref('')
const typeId = ref(0)
const getOrderStat = () => {
badgeApi.getOrderStat({}).then((res: any) => {
orderStat.value = res
prodId.value = getDefaultProdId(res)
if (res.type_id == 4) {
getKindList()
}
prop.value = res.prop
typeId.value = res.type_id
getSundryList()
})
}
const getDefaultProdId = (res: any) => {
if (res.type_id == 4) {
return res.prod_id == 7 || res.prod_id == 1 ? 1 : 19
} else if (res.type_id == 6) {
return res.prod_id == 7 || res.prod_id == 25 ? 25 : 24
} else if (res.type_id == 3) {
return res.prod_id == 7 ? 7 : 8
} else {
return res.prod_id == 7 ? 7 : 8
}
}
const kindList = ref([])
const getKindList = () => {
badgeApi.getKindList({
support_subject: getSupportSubject(),
type_id: 4
}).then((res: any) => {
kindList.value = res.list
kindId.value = res.list && res.list[0]?.id
console.log('kindList', kindList.value)
})
}
const getSupportSubject = () => {
if (prodId.value == 19 || prodId.value == 24 || prodId.value == 8) {
return 2
} else {
return 1
}
}
const prodChange = (id: number) => {
if (typeId.value == 3 && id == 7) {
showToast(toValueWithout('人物3D冰箱贴暂未开放'))
return
}
if (typeId.value == 4) {
if(prodId.value == 19) {
showToast(toValueWithout('该订单属于宠物卡通手办'))
return
}
if(prodId.value == 1) {
showToast(toValueWithout('该订单属于人物卡通手办'))
return
}
}
if (typeId.value == 6) {
prodId.value = id == 8 ? 24 : 25
} else if (typeId.value == 4) {
prodId.value = id == 8 ? 19 : 1
} else {
prodId.value = id == 7 ? 7 : 8
}
console.log('prodChange', prodId.value)
picture.value = null
kindId.value = 0
}
const kindId = ref(0)
const kindChange = (id: number) => {
kindId.value = id
console.log('kindChange', id)
}
const isLoading = ref(false)
// 生成图片
function goToPreview() {
if (isLoading.value) {
return
}
console.log('goToPreview')
if (!imgurl.value) {
showToast(toValueWithout('请先上传照片'))
return
}
if (orderStat.value.remain_count <= 0) {
showToast(toValueWithout('剩余次数不足'))
return
}
isLoading.value = true
if (trialCode.value) {
trialPhone()
} else {
getPid()
}
}
// 一次性体验链接
const trialPhone = async () => {
if (!mobile.value) {
showToast(toValueWithout('请输入手机号'));
return;
}
const mobileReg = /^1[3-9]\d{9}$/;
if (!mobileReg.test(mobile.value)) {
showToast(toValueWithout('请输入正确的手机号'));
return;
}
await badgeApi.trialPhone({
phone: mobile.value
}).then((res: any) => {
const timestamp = Date.now()
const payload = JSON.stringify({ userId: res, timestamp })
userId.value = Encrypt(payload)
localStorage.set('userId', userId.value)
getPid()
}).catch((err) => {
showToast({
message: err.message,
duration: 2000,
})
isLoading.value = false
}).finally(() => {
isLoading.value = false
})
}
const picture = ref(null)
let imgPromise = null;
const imageWidth = ref(0);
const imageHeight = ref(0);
function getbase64Data(data) {
console.log('getbase64Data', data)
picture.value = data;
const img = new Image();
img.src = data;
img.onload = () => {
imageWidth.value = img.naturalWidth;
imageHeight.value = img.naturalHeight;
if (imageWidth.value < 300 || imageHeight.value < 300) {
showToast(toValueWithout('请上传尺寸大于300*300像素的照片'))
imgurl.value = null
picture.value = null
return false;
}
const blob = base64ToBlob(data);
if (blob.size > 1024 * 1024 * 10) {
showToast(toValueWithout('照片大小不能超过10M'))
imgurl.value = null
picture.value = null
return false;
}
imgurl.value = blob;
if (prodId.value == 7 || prodId.value == 1 || prodId.value == 25) {
getUploadUrl()
}
}
}
function base64ToBlob(base64) {
var arr = base64.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {
type: mime
});
}
function imgorigoinf(data) {
const img = new Image();
const objectUrl = URL.createObjectURL(data); // 创建文件对象的URL
img.src = objectUrl;
img.onload = () => {
imageWidth.value = img.naturalWidth;
imageHeight.value = img.naturalHeight;
if (imageWidth.value < 300 || imageHeight.value < 300) {
showToast(toValueWithout('请上传尺寸大于300*300像素的照片'))
setTimeout(() => {
const btn = document.querySelector('.btn')
if (btn) {
btn.click()
}
}, 1500);
imgurl.value = null
picture.value = null
return
}
console.log('imageWidth', imageWidth.value, 'imageHeight', imageHeight.value)
}
}
const getUploadUrl = () => {
badgeApi.getUploadUrl({}).then((res: any) => {
if (res) {
sendFaceToOss(imgurl.value, res.upload_url, res.path)
}
})
}
// 人脸识别上传
const sendFaceToOss = async (src: string, url: string, path: string) => {
try {
const response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'image/jpeg'
},
body: src
})
if (!response.ok) {
throw new Error('Upload failed')
}
console.log('图片上传成功--------', response)
if (response.status == 200) {
badgeApi.faceCheck({
path: path,
prop: prop.value,
prod_id: prodId.value
}).then((res: any) => {
console.log('faceCheck', res)
showSuccessToast({
message: toValueWithout('人脸检测成功'),
duration: 2000,
})
}).catch((err) => {
console.log('faceCheck', err)
showFailToast({
message: err.message || toValueWithout('人脸检测失败'),
icon: 'none',
duration: 2000,
})
imgurl.value = null
picture.value = null
}).finally(() => {
setTimeout(() => {
closeToast()
}, 2000);
})
}
} catch (err) {
closeToast()
showFailToast({
message: err.message || toValueWithout('上传失败'),
icon: 'none',
duration: 2000,
})
}
}
const onOversize = (file: any) => {
if (file.size > 10 * 1024 * 1024) {
showToast(toValueWithout('照片大小不能超过10M'))
return false
}
return true
}
const beforeRead = (file: any) => {
const MIN_WIDTH = 300
const MIN_HEIGHT = 300
const MAX_SIZE = 10 * 1024 * 1024
const toPreviewAndCheck = (input: any): Promise<UploaderFileListItem> => {
return new Promise((resolve, reject) => {
const raw: File = input.file || input
if (raw.size > MAX_SIZE) {
showToast(toValueWithout('照片大小不能超过10M'))
reject(false)
return
}
const objectUrl = URL.createObjectURL(raw)
const img = new Image()
img.src = objectUrl
img.onload = () => {
const width = img.naturalWidth
const height = img.naturalHeight
imageWidth.value = width
imageHeight.value = height
URL.revokeObjectURL(objectUrl)
if (width < MIN_WIDTH || height < MIN_HEIGHT) {
showToast(toValueWithout(`请上传尺寸大于${MIN_WIDTH}×${MIN_HEIGHT}像素的照片`))
reject(false)
return
}
const reader = new FileReader()
reader.onload = (e) => {
const url = e.target?.result as string
// 设置回显,不手动 push,交由 v-model 维护
const out: any = input
out.url = url
resolve(out)
}
reader.onerror = () => {
showToast(toValueWithout('图片读取失败'))
reject(false)
}
reader.readAsDataURL(raw)
}
img.onerror = () => {
showToast(toValueWithout('图片加载失败'))
URL.revokeObjectURL(objectUrl)
reject(false)
}
})
}
if (Array.isArray(file)) {
return Promise.allSettled(file.map(toPreviewAndCheck)).then((results) => {
const passed = results
.filter(r => r.status === 'fulfilled')
.map((r: any) => r.value)
if (passed.length === 0) return Promise.reject(false)
return passed
})
}
return toPreviewAndCheck(file)
}
// 获取Pid
const pid = ref('')
const getPid = async () => {
showLoadingToast({
message: toValueWithout('上传中...'),
forbidClick: true,
loadingType: 'spinner',
duration: 0,
})
const params = {
prod_id: prodId.value,
extend_value: -1,
type_id: typeId.value
}
try {
const res = await badgeApi.getPid(params) as any
console.log('getPid', res)
isLoading.value = false
pid.value = res.pid
try {
const uploadTasks = [
sendToOss(imgurl.value, res.url),
// sendToOss(imgurl.value, res.oss_url)
]
await Promise.all(uploadTasks)
if (typeId.value == 4 && prodId.value == 19 && referPicture.value.length > 0) {
const multiUrlTasks = referPicture.value.map((item) => {
return badgeApi.getMultiUrl({
pid: pid.value,
}).then((res: any) => {
console.log('multiUrl', res)
return sendToOss(item.file, res.multi_url)
})
})
await Promise.all(multiUrlTasks)
}
} catch (err) {
closeToast()
console.error('上传失败:', err)
showFailToast({
message: toValueWithout('上传失败'),
duration: 2000,
})
isLoading.value = false
return
}
} catch (err) {
isLoading.value = false
closeToast()
showToast({
message: err.message,
duration: 2000,
})
console.log(err)
} finally {
isLoading.value = false
}
}
// 上传到OSS
const pendingUploads = ref(0)
const isAnotherAPICalled = ref(false)
const sendToOss = async (src: string | Blob | File, url: string) => {
try {
pendingUploads.value++
const response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'image/jpeg'
},
body: src
})
if (!response.ok) {
throw new Error('Upload failed')
}
console.log('图片上传成功--------', response)
if (--pendingUploads.value === 0 && !isAnotherAPICalled.value) {
isAnotherAPICalled.value = true
const params = {
pid: pid.value,
group: 1,
prod_id: prodId.value,
extend_value: -1,
type_id: typeId.value,
kind_id: kindId.value
}
badgeApi.putModeling(params).then((res: any) => {
console.log('putModeling', res)
createLog()
}).catch((err) => {
console.log('putModeling', err)
}).finally(() => {
closeToast()
})
}
} catch (err) {
closeToast()
pendingUploads.value--
showFailToast({
message: err.message,
duration: 2000,
})
console.log("uploadImage----err", err)
}
}
// 创建日志
const createLog = () => {
const params = {
pid: pid.value,
group: 1,
prod_id: prodId.value,
type_id: typeId.value,
kind_id: kindId.value
}
badgeApi.createLog(params).then((res: any) => {
console.log('createLog', res)
closeToast()
showSuccessToast({
message: toValueWithout('照片上传成功'),
duration: 2000,
})
setTimeout(() => {
closeToast()
router.push({
path: '/badge/preview',
query: {
pid: pid.value,
group: 1,
prod_id: prodId.value,
type_id: typeId.value,
kind_id: kindId.value
},
})
}, 1000);
}).catch((err) => {
console.log('getGenImages', err)
showFailToast({
message: err.message,
duration: 2000,
})
}).finally(() => {
closeToast()
})
}
// 获取首页图片上传指南图片
const sundryList = ref([])
const getSundryList = () => {
badgeApi.getSundryList({
page: 1,
size: 6,
group: prodId.value == 7 || prodId.value == 1 || prodId.value == 25 ? 'gp' : 'pet',
}).then((res: any) => {
const data = res || []
sundryList.value = data.list
}).catch((err) => {
showToast({
message: err.message,
duration: 2000,
})
}).finally(() => {
})
}
// 判断是否是一次性体验链接
const trialCode = ref(false)
const trialCodeValue = ref('')
const getTrialCode = () => {
const value = localStorage.get('trialCode')
if (value) {
trialCode.value = true
trialCodeValue.value = value
} else {
getOrderStat()
getSizeList()
}
}
// 手机号输入框
const mobile = ref('')
const userId = ref('')
const onBlur = async () => {
console.log(mobile.value);
if (!mobile.value) {
showToast(toValueWithout('请输入手机号'));
return;
}
const mobileReg = /^1[3-9]\d{9}$/;
if (!mobileReg.test(mobile.value)) {
showToast(toValueWithout('请输入正确的手机号'));
return;
}
}
const styleChange = (id: number) => {
prodId.value = id
getSundryList()
}
const key = CryptoJS.enc.Utf8.parse("123abcdefpua2025");
const iv = CryptoJS.enc.Utf8.parse('DYgjCEIMVrj2W9xN');
function Encrypt(word: string): string {
let srcs = CryptoJS.enc.Utf8.parse(word);
let encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
return encrypted.ciphertext.toString();
}
function handleBeforeUnload(_event: BeforeUnloadEvent) {
// code 的清除已在 main.ts 中统一处理,这里只清除 userId(如果需要)
// 注意:刷新时不应该清除 userId,只有关闭时才清除
// 由于无法精确区分刷新和关闭,这里保留清除逻辑,但主要依赖 main.ts 的处理
// localStorage.remove('userId')
}
onMounted(() => {
getSundryList()
openApp()
getTrialCode()
window.addEventListener('beforeunload', handleBeforeUnload);
})
</script>
<style lang="scss" scoped>
.photo-upload-page {
height: auto;
overflow-y: scroll!important;
}
.badge-size {
padding: 16px;
.size-title {
font-size: 14px;
color: #000;
}
.size-options {
.size-item {
margin-top: 8px;
display: flex;
justify-content: space-between;
align-items: center;
.size-text {
font-size: 14px;
color: #333;
text-align: center;
padding: 4px 10px;
background: #F0F2F5;
border-radius: 4px;
margin-right: 5px;
display: inline-block;
}
.size-count {
font-size: 12px;
color: #999;
display: inline-block;
}
}
}
}
.badge-info {
display: flex;
justify-content: space-between;
padding: 0 16px 16px 16px;
.badge-item:first-child {
background-image: url('@/assets/badge/sheji.png');
background-size: cover;
background-position: center;
margin-right: 5px;
}
.badge-item:last-child {
background-image: url('@/assets/badge/order.png');
background-size: cover;
background-position: center;
margin-left: 5px;
}
.badge-item {
padding: 18px 12px;
flex: 1;
text-align: left;
cursor: pointer;
height: 88px;
.badge-title {
font-size: 16px;
color: #000;
font-weight: bold;
}
.badge-count {
font-size: 15px;
color: #333;
font-weight: bold;
margin-top: 8px;
}
}
}
.step-container {
display: flex;
align-items: flex-start;
padding: 16px 0 16px 16px;
}
.step-item {
display: flex;
align-items: center;
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.step-item.active {
flex: 2;
margin-left: 50px;
}
.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;
}
.step-title {
font-size: 16px;
color: #808080;
}
.step-desc {
font-size: 12px;
color: #808080;
margin-top: 2px;
}
.photo-upload-body {
position: relative;
width: 80vw;
height: 80vw;
margin: 16px auto 0 auto;
text-align: center;
}
.photo-upload-box {
margin: 16px auto 0 auto;
width: 80vw;
height: 80vw;
border-radius: 12px;
background: #F0F2F5;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
}
.photo-upload-box-1 {
width: 80vw;
height: 70vw;
border-radius: 12px;
/* background: #F0F2F5; */
background: rgba(0, 0, 0, 0);
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.photo-upload-img {
max-width: 80vw;
max-height: 80vw;
border-radius: 12px;
}
.photo-upload-header {
width: 100%;
display: flex;
align-items: center;
padding: 12px 16px 0 12px;
font-size: 14px;
color: #888;
justify-content: flex-end;
}
.photo-upload-guide-icon {
font-size: 16px;
margin-right: 4px;
}
.photo-upload-guide-text {
font-size: 13px;
}
.photo-upload-area {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
user-select: none;
}
.photo-upload-plus {
font-size: 48px;
color: #c2c2c2;
margin-bottom: 0;
}
.photo-upload-text {
font-size: 16px;
color: #888;
margin-bottom: 4px;
}
.photo-upload-tip {
font-size: 12px;
color: #b0b0b0;
}
.photo-upload-footer {
position: absolute;
bottom: 0px;
left: 0;
width: 100%;
text-align: center;
font-size: 12px;
color: #b0b0b0;
padding: 0 12px;
line-height: 1.5;
}
.style-section {
margin: 16px 0 0 0;
padding: 0 16px;
}
.style-title {
font-size: 16px;
color: #222;
font-weight: bold;
margin-bottom: 12px;
}
.style-list {
display: flex;
gap: 10px;
justify-content: flex-start;
align-items: flex-start;
overflow-x: scroll;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
.style-list::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
.style-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
position: relative;
width: 25vw;
}
.style-img {
width: 23vw;
height: 23vw;
border-radius: 8px;
border: 2px solid transparent;
object-fit: cover;
transition: border 0.2s;
}
.style-item .style-selected {
position: absolute;
right: 4px;
bottom: 30px;
width: 20px;
height: 20px;
background: #50cf54;
border-radius: 50%;
border: 2px solid #fff;
display: inline-block;
box-shadow: 0 2px 8px rgba(80, 207, 84, 0.2);
}
.style-item .style-selected::after {
content: '';
display: block;
width: 10px;
height: 6px;
border-left: 2px solid #fff;
border-bottom: 2px solid #fff;
position: absolute;
left: 4px;
top: 5px;
transform: rotate(-45deg);
}
.style-item .style-label {
margin-top: 8px;
font-size: 14px;
color: #333;
text-align: center;
white-space: nowrap;
}
.style-item:hover .style-img,
.style-item .style-selected ~ .style-img {
border: 2px solid #50cf54;
}
.bg-style-section {
margin: 24px 0 0 0;
padding: 0 16px;
}
.bg-style-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.bg-style-title {
font-size: 16px;
color: #000;
font-weight: bold;
margin-right: 8px;
}
.bg-style-desc {
font-size: 13px;
color: #808080;
display: flex;
align-items: center;
cursor: not-allowed;
}
.bg-style-desc.disabled {
opacity: 0.6;
}
.bg-style-tabs {
display: flex;
gap: 10px;
margin-bottom: 8px;
}
.bg-style-tab {
padding: 6px 18px;
border: none;
border-radius: 18px;
background: #f0f2f5;
color: #333;
font-size: 12px;
cursor: pointer;
outline: none;
transition: background 0.2s, color 0.2s;
}
.bg-style-tab.active {
background: #e6f7e6;
color: #22c55e;
font-weight: bold;
}
.bg-style-tab:disabled {
background: #f0f2f5;
color: #b0b0b0;
cursor: not-allowed;
}
.bg-style-tip {
font-size: 13px;
color: #888;
margin-top: 4px;
}
.bg-photo-upload-section {
margin: 24px 0 0 0;
padding: 0 16px;
}
.bg-photo-upload-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 16px;
font-weight: bold;
color: #222;
margin-bottom: 12px;
}
.bg-photo-upload-area {
display: flex;
align-items: center;
position: relative;
}
.bg-photo-upload-label {
display: inline-block;
}
.bg-photo-upload-label-1 {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 120px;
height: 120px;
background: none;
}
.bg-photo-upload-box {
width: 120px;
height: 120px;
border: 2px dashed #b0b0b0;
border-radius: 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
background: #fafafa;
transition: border 0.2s;
}
.bg-photo-upload-img {
width: 120px;
height: 120px;
border-radius: 12px;
}
.bg-photo-upload-plus {
font-size: 32px;
color: #b0b0b0;
margin-bottom: 8px;
}
.bg-photo-upload-text {
font-size: 16px;
color: #888;
}
.design-action-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
background: #fff;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
}
.design-left {
display: flex;
align-items: center;
}
.design-leaf-icon {
margin-right: 4px;
vertical-align: middle;
}
.design-remaining {
color: #222;
font-size: 16px;
font-weight: 500;
}
.design-btn {
display: flex;
align-items: center;
justify-content: center;
border: none;
outline: none;
cursor: pointer;
width: 50vw;
height: 56px;
border-radius: 28px;
font-size: 20px;
font-weight: bold;
background: linear-gradient(90deg, #D1ED8E 0%, #55E668 100%);
color: #222;
box-shadow: 0 2px 8px rgba(80, 207, 84, 0.10);
transition: background 0.2s;
}
.design-btn .design-arrow {
margin-left: 20px;
vertical-align: middle;
position: absolute;
right: 30px;
transition: transform 0.2s;
width: 18px;
height: 12px;
}
.btndiv {
position: fixed;
bottom: 120px;
width: 80%;
left: 50%;
transform: translateX(-50%);
}
@media screen and (max-width: 767px) {
_::-webkit-full-page-media,
_:future,
:root .btndiv {
padding-bottom: 120px;
}
}
.order-type-item {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
border-radius: 12px;
margin-bottom: 12px;
font-size: 14px;
}
.style-box {
padding: 0 16px;
}
.style-box-item {
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 12px;
font-size: 14px;
margin-bottom: 12px;
}
.style-item-title {
width: 48%;
text-align: center;
padding: 10px 0;
border-radius: 12px;
color: #333;
font-size: 14px;
background: #f5f5f5;
}
.style-item-title.styleActive {
background: #50cf54;
color: #fff;
}
.tag-change-box {
margin: 0 auto;
}
.tag-change-item {
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
padding: 10px 0;
}
.tag-change-item-title {
font-size: 14px;
color: #333;
background: #E5E5E5;
width: 120px;
height: 32px;
text-align: center;
line-height: 32px;
}
.tag-change-item-title:first-child {
border-top-left-radius: 16px;
border-bottom-left-radius: 16px;
}
.tag-change-item-title:last-child {
border-top-right-radius: 16px;
border-bottom-right-radius: 16px;
}
.tag-change-item-title.tagActive {
background: #50CF54;
color: #fff;
}
.kind-box {
display: flex;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
padding: 16px 36px;
}
.kind-box-item {
margin-right: 10px;
padding: 6px 10px;
background: #f5f5f5;
border-radius: 8px;
color: #333;
}
.kind-item-title {
font-size: 14px;
}
.kind-box-item.styleActive {
background: #50CF54;
color: #fff;
}
.photo-upload-box-refer {
margin: 16px;
}
</style>