Browse Source

新增徽章形状,裁剪优化

lzm_web
Linzm 7 months ago
parent
commit
e330ba2ce8
  1. 4
      .env.development
  2. 1
      components.d.ts
  3. 24
      src/api/badge.ts
  4. 4
      src/assets/font/font.css
  5. BIN
      src/assets/font/jingnan-Nunito.ttf
  6. 1
      src/main.ts
  7. 44
      src/views/badge/index.vue
  8. 88
      src/views/badge/orderDetail.vue
  9. 342
      src/views/badge/preview.vue

4
.env.development

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
VITE_APP_PREVIEW=true
# VITE_APP_API_BASE_URL=http://172.16.0.47:8166/web/
# VITE_APP_API_BASE_URL=https://wechat-test.api.puabadge.com/web/
VITE_APP_API_BASE_URL=https://wechat.api.puabadge.com/web/
VITE_APP_API_BASE_URL=https://wechat-test.api.puabadge.com/web/
# VITE_APP_API_BASE_URL=https://wechat.api.puabadge.com/web/
# VITE_APP_API_WX_URL=https://wechat.api.puabadge.com/web/
# http://web.suwa3d.dev:28499/
VITE_HTTP_MOCK=true

1
components.d.ts vendored

@ -24,7 +24,6 @@ declare module '@vue/runtime-core' { @@ -24,7 +24,6 @@ declare module '@vue/runtime-core' {
VanDivider: typeof import('vant/es')['Divider']
VanField: typeof import('vant/es')['Field']
VanIcon: typeof import('vant/es')['Icon']
VanImage: typeof import('vant/es')['Image']
VanList: typeof import('vant/es')['List']
VanPullRefresh: typeof import('vant/es')['PullRefresh']
VanStepper: typeof import('vant/es')['Stepper']

24
src/api/badge.ts

@ -127,3 +127,27 @@ export const faceCheck = (params: any) => { @@ -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
src/assets/font/font.css

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
@font-face {
font-family: "chinese";
src: url('jingnan-Nunito.ttf');
}

BIN
src/assets/font/jingnan-Nunito.ttf

Binary file not shown.

1
src/main.ts

@ -7,6 +7,7 @@ import router from './router' @@ -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'

44
src/views/badge/index.vue

@ -12,10 +12,15 @@ const show = ref(false) @@ -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) { @@ -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) { @@ -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(() => { @@ -795,7 +804,7 @@ onMounted(() => {
}
.step-title {
font-size: 16px;
color: #808080;;
color: #808080;
}
.step-desc {
font-size: 12px;
@ -808,6 +817,7 @@ onMounted(() => { @@ -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(() => { @@ -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 {

88
src/views/badge/orderDetail.vue

@ -5,8 +5,10 @@ @@ -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 @@ @@ -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(() => { @@ -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) { @@ -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;

342
src/views/badge/preview.vue

@ -27,18 +27,16 @@ @@ -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 @@ @@ -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() { @@ -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 = () => { @@ -255,6 +271,7 @@ const confirm = () => {
showToast('请先选择下单数量')
return
}
getPosition()
loading.value = true
showConfirmDialog({
title: '确认下单',
@ -267,7 +284,9 @@ const confirm = () => { @@ -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 = () => { @@ -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 = () => { @@ -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);
// 21
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(() => { @@ -460,6 +644,7 @@ onMounted(() => {
// pid.value = 281505;
// group.value = 1;
// prodId.value = 7;
getTypesList()
getImageList()
progressList()
getCompareImage()
@ -594,22 +779,54 @@ onUnmounted(() => { @@ -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(() => { @@ -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>
Loading…
Cancel
Save