新增徽章形状,裁剪优化

This commit is contained in:
Linzm committed 2025-07-28 09:06:32 +08:00
1 parent 53add7b47a
commit e330ba2ce8
9 files changed
+448 -60

No files matched your search

+24
View File
@@ -127,3 +127,27 @@ export const faceCheck = (params: any) => {
params,
})
}
// 获取形状列表
export const getShapeList = (params: any) => {
return request('products/shapeList', {
method: 'GET',
params,
})
}
// 获取形状类别列表
export const getTypesList = (params: any) => {
return request('products/typesList', {
method: 'GET',
params,
})
}
// 上传合成图
export const composite = (data: any) => {
return request('uploads/composite', {
method: 'POST',
data,
})
}
+4
View File
@@ -0,0 +1,4 @@
@font-face {
font-family: "chinese";
src: url('jingnan-Nunito.ttf');
}
Binary file not shown.
+1
View File
@@ -7,6 +7,7 @@ import router from './router'
import './app.less'
import './assets/icon/iconfont.css'
import "./assets/font/font.css";
// import I18n from "@/lang/i18n"
import I18NextVue from 'i18next-vue'
+27 -17
View File
@@ -12,10 +12,15 @@ const show = ref(false)
const imgShow = ref(false)
const router = useRouter()
const option = ref({
canScale: true,
autoCropWidth: 1024,
autoCropHeight: 1024,
ceilbutton: false,
infoTrue: true,
fixed: false,
fixedNumber: [3, 4],
fixedBox: false,
canMoveBox: true,
enlarge: 6
})
const options = ref({
@@ -135,19 +140,23 @@ function getbase64Data(data) {
img.onload = () => {
imageWidth.value = img.naturalWidth;
imageHeight.value = img.naturalHeight;
if (imageWidth.value < 800 || imageHeight.value < 800) {
showToast('请上传尺寸大于800*800像素的照片')
return
if (imageWidth.value < 500 || imageHeight.value < 500) {
showToast('请上传尺寸大于500*500像素的照片')
imgurl.value = null
picture.value = null
return false;
}
const blob = base64ToBlob(data);
if (blob.size > 1024 * 1024 * 10) {
showToast('照片大小不能超过10M')
imgurl.value = null
picture.value = null
return false;
}
imgurl.value = blob;
if (prodId.value == 7) {
getUploadUrl()
}
}
const blob = base64ToBlob(data);
if (blob.size > 1024 * 1024 * 10) {
showToast('照片大小不能超过10M')
return
}
imgurl.value = blob;
if (prodId.value == 7) {
getUploadUrl()
}
}
@@ -172,8 +181,8 @@ function imgorigoinf(data) {
img.onload = () => {
imageWidth.value = img.naturalWidth;
imageHeight.value = img.naturalHeight;
if (imageWidth.value < 800 || imageHeight.value < 800) {
showToast('请上传尺寸大于800*800像素的照片')
if (imageWidth.value < 500 || imageHeight.value < 500) {
showToast('请上传尺寸大于500*500像素的照片')
setTimeout(() => {
const btn = document.querySelector('.btn')
if (btn) {
@@ -795,7 +804,7 @@ onMounted(() => {
}
.step-title {
font-size: 16px;
color: #808080;;
color: #808080;
}
.step-desc {
font-size: 12px;
@@ -808,6 +817,7 @@ onMounted(() => {
width: 80vw;
height: 80vw;
margin: 16px auto 0 auto;
text-align: center;
}
.photo-upload-box {
margin: 16px auto 0 auto;
@@ -833,8 +843,8 @@ onMounted(() => {
right: 0;
}
.photo-upload-img {
width: 80vw;
height: 80vw;
max-width: 80vw;
max-height: 80vw;
border-radius: 12px;
}
.photo-upload-header {
+69 -19
View File
@@ -5,8 +5,10 @@
</div>
<div class="order-status">
<div class="avatar-wrapper">
<img class="avatar-img" :src="order.path" :alt="order.kind_name" />
<div class="progress-image-item-shadow"></div>
<!-- <img v-if="shapeImage" class="avatar-back-img" :src="shapeImage" alt=""> -->
<div class="avatar-front-box">
<img class="avatar-font-img" :src="imageUrl" :alt="order.kind_name" />
</div>
</div>
</div>
<div class="status-info">
@@ -22,16 +24,20 @@
<div class="product-title">产品信息</div>
<div class="product-details">
<div class="product-row">
<span class="product-label">产品名称</span>
<span class="product-value">立体徽章</span>
<span class="product-label">产品名称</span>
<span class="product-value">立体徽章</span>
</div>
<div class="product-row">
<span class="product-label">尺寸</span>
<span class="product-value">{{order.model_size}}</span>
<span class="product-label">尺寸</span>
<span class="product-value">{{order.model_size}}</span>
</div>
<div class="product-row" v-if="shape_name">
<span class="product-label">形状</span>
<span class="product-value">{{shape_name}}</span>
</div>
<div class="product-row">
<span class="product-label">购买数量</span>
<span class="product-value">{{order.count}}</span>
<span class="product-label">购买数量</span>
<span class="product-value">{{order.count}}</span>
</div>
</div>
</div>
@@ -80,13 +86,42 @@ onMounted(() => {
id.value = route.query.id
getOrderDetail()
})
const shapeImage = ref('')
const imageUrl = ref('')
const getStyle = ref('')
const shape_name = ref('')
function getOrderDetail() {
badgeApi.getOrderDetail({
id: id.value,
}).then(res => {
order.value = res
imageUrl.value = res.path
shapeImage.value = res.shape_details.frame_path
shape_name.value = res.shape_details.name
// if (res.shape_details) {
// ImageShow(res.shape_details)
// }
})
}
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;transform: rotate3D(1, 1, 0, 0deg)`
}
}
const router = useRouter();
function goBack() {
router.back()
@@ -121,23 +156,38 @@ function getStatus(status: number) {
cursor: pointer;
}
.order-status {
margin: 0 auto;
width: 100vw;
height: 100vw;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 32px;
flex-direction: column;
}
.avatar-wrapper {
width: 85vw;
height: 85vw;
border-radius: 50%;
margin-bottom: 24px;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: 320px;
height: 320px;
}
.avatar-back-img {
position: absolute;
width: 320px;
height: 320px;
top: 0;
left: 0;
z-index: 2;
}
.avatar-front-box {
width: 320px;
height: 320px;
position: absolute;
z-index: 1;
}
.avatar-font-img {
width: 100%;
height: auto;
border-radius: 10px;
}
.progress-image-item-shadow {
position: absolute;
top: 0;
+321 -21
View File
@@ -27,18 +27,16 @@
<div class="progress-text">{{progressText}} {{progress}}%</div>
<div class="progress-desc">总计大约需要90秒,请耐心等待...</div>
</div>
<div v-else class="progress-section">
<!-- <van-swipe v-model="currentIndex" class="progress-section-img" :autoplay="3000" :show-indicators="false">
<van-swipe-item v-for="item in imageList" :key="item.id">
<img class="progress-section-img" :src="item.origin_url" alt="">
<div class="progress-image-item-shadow"></div>
</van-swipe-item>
</van-swipe> -->
<div class="progress-section-img">
<img class="progress-section-img-item" :src="imageUrl" alt="">
<div class="progress-image-item-shadow"></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" :class="{ 'box1-round-image': shapeId == 1 && shapeImage == '' }" :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 class="image-list-box">
<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">
@@ -64,6 +62,23 @@
<div class="info-content">材料</div>
</div>
</div>
<div class="shape-body">
<div class="shape-type">
<div class="shape-type-item" :class="{ 'shape-active': item.id == typeId }" v-for="item in typesList" :key="item.id" @click="typeChange(item.id)">{{item.name}}</div>
</div>
<div class="shape-box">
<div class="shape-item" v-for="item in shapeList" :key="item.id" @click="shapeChange(item)">
<div class="shape-item-list">
<img class="shape-item-image" :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>
</div>
<div class="shape-box-input" v-if="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打印色彩与图片有合理色差,如对产品有疑问,可以联系客服咨询。
@@ -141,7 +156,8 @@ function compare() {
}
function sureReload() {
if (payAmount.value <= 0) {
console.log('orderStat', orderStat.value)
if (orderStat.value.remain_count <= 0) {
showToast('当前次数已用完,请重新购买')
return
}
@@ -255,6 +271,7 @@ const confirm = () => {
showToast('请先选择下单数量')
return
}
getPosition()
loading.value = true
showConfirmDialog({
title: '确认下单',
@@ -267,7 +284,9 @@ const confirm = () => {
key: imgKey.value,
pay_amount: payAmount.value,
products: sizeList.value,
prod_id: prodId.value
prod_id: prodId.value,
shape_id: shapeId.value,
custom_text: shapeText.value
}
badgeApi.creatOrder(parms).then((res: any) => {
console.log('creatOrder', res)
@@ -297,6 +316,21 @@ const confirm = () => {
}
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);
// 轮询获取图片
@@ -426,15 +460,165 @@ const getImageList = () => {
})
}
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({
type_id: typeId.value,
prod_id: prodId.value
}).then((res: any) => {
console.log('getShapeList', res)
shapeList.value = res.list
shapeId.value = res.list[0].id
shapeImage.value = res.list[0]?.frame_path
custom_switch.value = res.list[0]?.custom_switch
limitCount.value = res.list[0]?.text_limit_max
ImageShow(res.list[0])
if (custom_switch.value == 1) {
textShow(res.list[0])
}
}).catch((err) => {
console.log('getShapeList', err)
}).finally((err) => {
})
}
const getStyle = ref('')
const ImageShow = (item: any) => {
const scale = 0.6;
const img = new Image()
img.src = item.frame_path
img.onload = () => {
console.log('img', img)
const ratioWidth = 320 / img.width;
const ratioHeight = 320 / img.height;
const x = item.axisx * ratioWidth * scale;
const y = item.axisy * ratioHeight * scale;
const path_width = item.width * ratioWidth * scale;
const path_height = item.height * ratioHeight * scale;
getStyle.value = `left: ${x}px;top: ${y}px;width: ${path_width}px;height: ${path_height}px;`
}
}
const shapeTextStyle = ref('')
const textShow = (item: any) => {
const scale = 0.6;
const img = new Image()
img.src = item.frame_path
img.onload = () => {
console.log('img', img)
const ratioWidth = 320 / img.width;
const ratioHeight = 320 / img.height;
const x = item.text_axisx * ratioWidth * scale;
const y = item.text_axisy * ratioHeight * scale;
const path_width = item.text_width * ratioWidth * scale;
const path_height = item.text_height * ratioHeight * scale;
const text_size = item.font_size * Math.min(ratioWidth * scale, ratioHeight * scale);
shapeTextStyle.value = `left: ${x}px;top: ${y}px;width: ${path_width}px;height: ${path_height}px;text-align: center;line-height: ${path_height}px;font-size: ${text_size}px;color: ${item.font_color};font-weight: ${item.font_weight};`
}
}
const shapeText = ref('')
const changeShapeText = (e: any) => {
console.log('changeShapeText', e.target.value)
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) => {
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()
getSizeList()
}
}
const orderStat = ref({})
const getOrderStat = () => {
badgeApi.getOrderStat({}).then((res: any) => {
console.log('getOrderStat', res)
orderStat.value = res
})
}
function handleBeforeUnload(_event: BeforeUnloadEvent) {
localStorage.remove('code')
localStorage.remove('trialCode')
@@ -460,6 +644,7 @@ onMounted(() => {
// pid.value = 281505;
// group.value = 1;
// prodId.value = 7;
getTypesList()
getImageList()
progressList()
getCompareImage()
@@ -594,22 +779,54 @@ onUnmounted(() => {
.progress-section {
margin: 0 auto;
text-align: center;
width: 80vw;
min-height: 80vw;
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 {
width: 80vw;
height: 80vw;
border-radius: 50%;
position: relative;
}
.progress-section-img-item {
width: 80vw;
height: 80vw;
.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 {
@@ -904,4 +1121,87 @@ onUnmounted(() => {
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: space-between;
}
.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);
}
.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;
}
</style>