单图徽章代码提交

This commit is contained in:
Linzm committed 2025-06-05 17:48:49 +08:00
1 parent 754b6f3834
commit 96f5bcec51
20 files changed
+1186 -672

No files matched your search

+61 -12
View File
@@ -1,15 +1,64 @@
import request from '@/utils/request'
// export const calc = (data: any) => {
// return request('queue/calc', {
// method: 'POST',
// data,
// })
// }
// 获取杂图
export const getSundryList = (params: any) => {
return request('products/sundryList', {
method: 'GET',
params,
})
}
// export const attr = (params: any) => {
// return request('queue/attr', {
// method: 'GET',
// params,
// })
// }
// 获取风格类型
export const getKindList = (params: any) => {
return request('products/kindList', {
method: 'GET',
params,
})
}
// 获取上传图片地址
export const getImageUrl = (params: any) => {
return request('images/uploadUrl', {
method: 'GET',
params,
})
}
// 获取图片
export const getImageList = (params: any) => {
return request('images/getList', {
method: 'GET',
params,
})
}
// 获取记录图集
export const getLogOage = (params: any) => {
return request('images/logPage', {
method: 'GET',
params,
})
}
// 获取我的订单
export const getMyOrder = (params: any) => {
return request('orders/page', {
method: 'GET',
params,
})
}
// 获取订单详情
export const getOrderDetail = (params: any) => {
return request('orders/details', {
method: 'GET',
params,
})
}
export const putMOdeling = (data: any) => {
return request('images/putModeling', {
method: 'POST',
data,
})
}
+1 -1
View File
@@ -1,3 +1,3 @@
export const config: any = {
brand: '3D摄影',
brand: '葡啊立体肖像',
}
+1 -1
View File
@@ -71,7 +71,7 @@ const en = {
"人请等待叫号": "people ahead of you, please wait for your turn.",
"返回": "Go back",
"排队成功": "Queue successful",
"3D摄影": "3D Photography",
"葡啊立体肖像": "3D Photography",
"排队": "Queue",
"登录": "Login",
"客户手机号和客户编号不能同时为空": "The customer's phone number and customer ID cannot both be empty at the same time.",
+1 -1
View File
@@ -70,7 +70,7 @@ const ko = {
"人请等待叫号": "명이 있습니다. 호출을 기다려 주세요",
"返回": "돌아오다",
"排队成功": "줄을 서서 성공하다",
"3D摄影": "3D 촬영",
"葡啊立体肖像": "3D 촬영",
"排队": "줄을 서다",
"登录": "로그인",
"客户手机号和客户编号不能同时为空": "고객 휴대폰 번호와 고객 번호는 동시에 비워 둘 수 없습니다",
+1 -1
View File
@@ -70,7 +70,7 @@ const zhCN = {
"人请等待叫号": "人请等待叫号",
"返回": "返回",
"排队成功": "排队成功",
"3D摄影": "3D摄影",
"葡啊立体肖像": "葡啊立体肖像",
"排队": "排队",
"登录": "登录",
"客户手机号和客户编号不能同时为空": "客户手机号和客户编号不能同时为空",
+1 -1
View File
@@ -69,7 +69,7 @@ const zhCT = {
"人请等待叫号": "人請等待叫號",
"返回": "返回",
"排队成功": "排隊成功",
"3D摄影": "3D攝影",
"葡啊立体肖像": "3D攝影",
"排队": "排隊",
"登录": "登錄",
"客户手机号和客户编号不能同时为空": "客户手機號碼與客戶編號不能同時為空",
+8 -38
View File
@@ -4,9 +4,7 @@ import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
// 导入路由组件
import * as badgeRouter from './badge'
import mian from '@/views/index.vue'
import Login from '@/views/login/index.vue'
import { useTokenStore } from '@/stores/token'
import badge from '@/views/badge/index.vue'
import {useStore} from '@/stores'
import { config } from '@/config/config'
@@ -16,21 +14,13 @@ NProgress.configure({ showSpinner: true, parent: '#app' })
const routes = [
{
path: '/',
name: 'main',
component: mian,
name: 'badge',
component: badge,
meta: {
needGuard: true,
title: '首页',
},
},
{
path: '/login',
name: 'login',
component: Login,
meta: {
title: '登录',
},
},
]
badgeRouter.mergeRoutes(routes)
@@ -41,11 +31,10 @@ const router = createRouter({
})
router.beforeEach((_to, _from, next) => {
const tokenStore = useTokenStore()
// const tokenStore = useTokenStore()
const store = useStore()
const token = _to.query.token
if (token) {
tokenStore.login(String(token))
const code = _to.query.code
if (code) {
let url = store.redirectUrl()
if (_to.path !== '/badge') {
@@ -67,28 +56,9 @@ router.beforeEach((_to, _from, next) => {
NProgress.start() // start progress bar
const needGuard = _to.meta.needGuard
if (needGuard) {
if (!tokenStore.isLoggedIn()) {
// 记录本地访问路径
store.setRedirect(_to.fullPath);
next(`/login`)
} else {
if (_to.path === '/') {
next('/badge')
} else {
next()
}
}
next()
} else {
if (_to.path === '/login') {
if (!tokenStore.isLoggedIn()) {
next()
} else {
next('/badge')
}
} else {
next()
}
next()
}
})
-329
View File
@@ -1,329 +0,0 @@
<script lang="ts">
import { defineComponent } from '@vue/runtime-dom'
import type { PropType } from '@vue/runtime-dom'
interface Style {
width?: string
height?: string
top?: string
left?: string
[key: string]: any
}
const tabHeight = 70
export default defineComponent({
name: 'Cropper',
props: {
avatarSrc: {
type: String as PropType<string>,
default: '',
},
avatarStyle: {
type: String as PropType<string>,
default: '',
},
selWidth: {
type: String as PropType<string>,
default: '',
},
selHeight: {
type: String as PropType<string>,
default: '',
},
expWidth: {
type: String as PropType<string>,
default: '',
},
expHeight: {
type: String as PropType<string>,
default: '',
},
minScale: {
type: String as PropType<string>,
default: '',
},
maxScale: {
type: String as PropType<string>,
default: '',
},
canScale: {
type: String as PropType<string>,
default: '',
},
noTab: {
type: String as PropType<string>,
default: '',
},
quality: {
type: String as PropType<string>,
default: '',
},
index: {
type: String as PropType<string>,
default: '',
},
},
data() {
return {
cvsStyleHeight: '0px',
styleDisplay: 'none',
styleTop: '-10000px',
prvTop: '-10000px',
imgStyle: {} as Style,
selStyle: {} as Style,
showOper: true,
imgSrc: {
imgSrc: '',
},
qlty: 0.9,
postWidthFirst: {},
}
},
watch: {
avatarSrc() {
this.imgSrc.imgSrc = this.avatarSrc
},
},
created() {
const canvas = document.getElementById('avatar-canvas')
const operCanvas = document.getElementById('oper-canvas')
this.ctxCanvas = canvas.getContext('2d')
this.ctxCanvasOper = operCanvas.getContext('2d')
this.qlty = parseInt(this.quality) || 0.9
this.imgSrc.imgSrc = this.avatarSrc
this.letScale = this.canScale === 'false' ? 0 : 1
this.indx = this.index || undefined
this.mnScale = this.minScale || 0.3
this.mxScale = this.maxScale || 4
this.noBar = this.noTab === 'true' ? 1 : 0
if (this.noBar) {
this.moreHeight = 0
this.fWindowResize()
} else {
this.moreHeight = 50
this.fWindowResize()
}
},
methods: {
fWindowResize() {
const sysInfo = {
platform: navigator.platform,
pixelRatio: window.devicePixelRatio,
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
windowTop: window.scrollY,
windowBottom: 0,
statusBarHeight: 0,
screenHeight: screen.height,
}
this.platform = sysInfo.platform
this.pixelRatio = sysInfo.pixelRatio
this.windowWidth = sysInfo.windowWidth
this.drawTop = sysInfo.windowTop
this.windowHeight = sysInfo.windowHeight + sysInfo.windowBottom
this.cvsStyleHeight = `${this.windowHeight - tabHeight}px`
this.pxRatio = this.windowWidth / 750
const style = this.avatarStyle
console.log(style)
this.imgStyle = style
this.expWidth && (this.exportWidth = this.expWidth.includes('rpx')
? parseInt(this.expWidth) * this.pxRatio
: parseInt(this.expWidth))
this.expHeight && (this.exportHeight = this.expHeight.includes('rpx')
? parseInt(this.expHeight) * this.pxRatio
: parseInt(this.expHeight))
if (this.styleDisplay === 'flex')
this.fDrawInit(true)
this.fHideImg()
},
fSelect() {
if (this.fSelecting)
return
this.fSelecting = true
setTimeout(() => {
this.fSelecting = false
}, 500)
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.onchange = (e) => {
const file = e.target.files[0]
if (!file)
return
const reader = new FileReader()
reader.onload = (e) => {
const path = e.target.result
this.imgPath = path
const img = new Image()
img.onload = () => {
this.imgWidth = img.width
this.imgHeight = img.height
this.path = path
if (!this.hasSel) {
const style = this.selStyle || {}
if (this.selWidth && this.selHeight) {
const selWidth = this.selWidth.includes('rpx')
? parseInt(this.selWidth) * this.pxRatio
: parseInt(this.selWidth)
const selHeight = this.selHeight.includes('rpx')
? parseInt(this.selHeight) * this.pxRatio
: parseInt(this.selHeight)
style.width = `${parseInt(selWidth)}px`
style.height = `${parseInt(selHeight)}px`
style.top = `${parseInt((this.windowHeight - parseInt(style.height) - tabHeight) / 2)}px`
style.left = `${parseInt((this.windowWidth - parseInt(style.width)) / 2)}px`
} else {
alert('裁剪框的宽或高没有设置')
return
}
this.selStyle = style
}
this.fDrawInit(true)
}
img.src = path
}
reader.readAsDataURL(file)
}
input.click()
},
fUpload() {
if (this.fUploading)
return
this.fUploading = true
setTimeout(() => {
this.fUploading = false
}, 1000)
const style = this.selStyle
let x = parseInt(style.left)
let y = parseInt(style.top)
const width = parseInt(style.width)
const height = parseInt(style.height)
let expWidth = this.exportWidth || width
let expHeight = this.exportHeight || height
x *= this.pixelRatio
y *= this.pixelRatio
expWidth = width
expHeight = height
this.styleDisplay = 'none'
this.styleTop = '-10000px'
this.hasSel = false
this.fHideImg()
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
ctx.drawImage(this.ctxCanvas.canvas, x, y, width, height, 0, 0, expWidth, expHeight)
canvas.toBlob((blob) => {
const url = URL.createObjectURL(blob)
this.$emit('upload', {
avatar: this.imgSrc,
path: url,
index: this.indx,
})
}, 'image/png', this.qlty)
},
// ... 其他方法保持不变
},
})
</script>
<template name="cropper">
<view>
<image :src="imgSrc.imgSrc" class="my-avatar" mode="aspectFill" :style="imgStyle" @click="fSelect" />
<!-- 上传图片 -->
<canvas
id="avatar-canvas" canvas-id="avatar-canvas" class="my-canvas" :style="{ top: styleTop, height: cvsStyleHeight }"
disable-scroll="false"
/>
<!-- 截取边框 -->
<canvas
id="oper-canvas" canvas-id="oper-canvas" class="oper-canvas" :style="{ top: styleTop, height: cvsStyleHeight }"
disable-scroll="false" @touchstart="fStart" @touchmove="fMove" @touchend="fEnd"
/>
<view class="oper-wrapper" :style="{ display: styleDisplay }">
<view v-if="showOper" class="btn-wrapper">
<view hover-class="hover" @click="fClose">
取消
</view>
<view hover-class="hover" @click="fUpload">
选取
</view>
</view>
</view>
</view>
</template>
<style lang="less">
.my-canvas {
display: flex;
position: fixed !important;
background: #000000;
left: 0;
z-index: 100;
width: 100%;
}
.my-avatar {
width: 100vw;
height: 100vw;
}
.oper-canvas {
display: flex;
position: fixed !important;
left: 0;
z-index: 101;
width: 100%;
}
.oper-wrapper {
height: 71px;
position: fixed !important;
box-sizing: border-box;
width: 100%;
left: 0;
bottom: 0;
z-index: 200;
flex-direction: row;
}
.btn-wrapper {
background-color: #000000;
color: #ffffff;
display: flex;
height: 100%;
width: 100%;
justify-content: space-around;
align-items: center;
}
.btn-wrapper view {
width: 160rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
font-size: 16px;
color: #ffffff;
z-index: 300;
}
.hover {
color: #f1f1f1;
}
</style>
+133 -44
View File
@@ -1,19 +1,18 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import H5Cropper from 'vue-cropper-h5'
import "vue-cropper-h5/dist/style.css"
import * as badgeApi from '@/api/badge'
import { onMounted, ref } from 'vue';
const show = ref(false)
const imgShow = ref(false)
const router = useRouter()
const imgurl = ref('') // 头像地址
const fileInput = ref<HTMLInputElement | null>(null)
function onUploadClick() {
if (fileInput.value)
fileInput.value.click()
}
function myUpload(rsp) {
const self = this
self.imgurl = rsp.path // 更新头像方式一
}
const option = ref({})
const options = ref({})
const imgurl = ref('')
const imgBgUrl = ref('')
const isBgShow = ref(true)
function goToRecord() {
router.push('/badge/record')
}
@@ -23,7 +22,59 @@ function goToMyOrder() {
function goToPreview() {
router.push('/badge/preview')
}
function onFileChange
function getbase64Data(data) {
imgurl.value = data;
console.log('图片地址', data)
}
function getBgbase64Data(data) {
imgBgUrl.value = data;
console.log('背景图片地址', data)
}
const getKindList = () => {
badgeApi.getKindList({
page: 1,
size: 20,
scale: 20,
}).then((res: any) => {
const data = res || []
}).catch((err) => {
}).finally((err) => {
})
}
const getSundryList = () => {
badgeApi.getSundryList({
page: 1,
size: 6,
group: 'gp',
}).then((res: any) => {
const data = res || []
}).catch((err) => {
}).finally((err) => {
})
}
const getList = () => {
badgeApi.getSundryList({
page: 1,
size: 5,
group: 'case',
ids: '6,7,8,9,19'
}).then((res: any) => {
const data = res || []
}).catch((err) => {
}).finally((err) => {
})
}
onMounted(() => {
getKindList()
getSundryList()
getList()
})
</script>
<template>
@@ -98,31 +149,30 @@ function onFileChange
</div>
</div>
</div>
<div 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">图片上传指南</span>
</div>
<div class="photo-upload-area" @click="onUploadClick">
<input
ref="fileInput"
type="file"
accept="image/*"
style="display: none"
@change="onFileChange"
>
<div class="photo-upload-plus">
+
<div class="photo-upload-body">
<div v-if="!imgurl" 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">图片上传指南</span>
</div>
<div class="photo-upload-text">
点击上传照片
<div class="photo-upload-area">
<div class="photo-upload-plus">
+
</div>
<div class="photo-upload-text">
点击上传照片
</div>
<div class="photo-upload-tip">
*上传照片时建议勾选[原图]
</div>
</div>
<div class="photo-upload-tip">
*上传照片时建议勾选[原图]
<div class="photo-upload-footer">
请确定您对上传的照片拥有合法使用权利或已取得他人合法授权,且同意本平台分析图片信息以提供生成服务
</div>
</div>
<div class="photo-upload-footer">
请确定您对上传的照片拥有合法使用权利或已取得他人合法授权,且同意本平台分析图片信息以提供生成服务
<img class="photo-upload-img" v-if="imgurl" :src="imgurl" alt="" srcset="">
<div class="photo-upload-box-1">
<h5-cropper :option="option" @getbase64Data="getbase64Data"></h5-cropper>
</div>
</div>
<div class="style-section">
@@ -198,38 +248,40 @@ function onFileChange
</div>
<div class="bg-style-tabs">
<button
class="bg-style-tab active"
type="button"
class="bg-style-tab" :class="isBgShow == true ? 'active' : ''"
type="button" @click="isBgShow = true"
>
照片原图背景
</button>
<button
class="bg-style-tab"
type="button"
disabled
class="bg-style-tab" :class="isBgShow == false ? 'active' : ''"
type="button" @click="isBgShow = false"
>
补充背景照片
</button>
</div>
<div class="bg-style-tip">
<div v-if="isBgShow == true" class="bg-style-tip">
选择该选项设计的徽章会根据上传的肖像照片背景进行生成效果
</div>
<div class="bg-style-tip">
<div v-if="isBgShow == false" class="bg-style-tip">
选择该选项需要额外上传一张照片作为背景
</div>
</div>
<div class="bg-photo-upload-section">
<div class="bg-photo-upload-section" v-if="isBgShow == false">
<div class="bg-photo-upload-header">
<span>补充背景照片</span>
</div>
<div class="bg-photo-upload-area">
<label class="bg-photo-upload-label">
<input type="file" accept="image/*" style="display:none">
<div v-if="!imgBgUrl" class="bg-photo-upload-label">
<div class="bg-photo-upload-box">
<div class="bg-photo-upload-plus">+</div>
<div class="bg-photo-upload-text">上传照片</div>
</div>
</label>
</div>
<img v-if="imgBgUrl" class="bg-photo-upload-img" :src="imgBgUrl" alt="">
<div class="bg-photo-upload-label-1">
<h5-cropper :option="options" @getbase64Data="getBgbase64Data"></h5-cropper>
</div>
</div>
</div>
<div style="height: 90px;" />
@@ -477,6 +529,12 @@ function onFileChange
margin-top: 2px;
}
.photo-upload-body {
position: relative;
width: 80vw;
height: 80vw;
margin: 16px auto 0 auto;
}
.photo-upload-box {
margin: 16px auto 0 auto;
width: 80vw;
@@ -489,6 +547,22 @@ function onFileChange
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 {
width: 80vw;
height: 80vw;
border-radius: 12px;
}
.photo-upload-header {
width: 100%;
display: flex;
@@ -687,10 +761,20 @@ function onFileChange
.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;
@@ -704,6 +788,11 @@ function onFileChange
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;
+40 -5
View File
@@ -1,10 +1,10 @@
<template>
<div class="my-order">
<div class="header" @click="goBack">
<span class="back-icon"><van-icon name="arrow-left" size="16px" /> 返回</span>
<span class="back-icon"><van-icon name="arrow-left" size="16px" color="#333" /> 我的订单</span>
</div>
<div class="order-list">
<div class="order-item" @click="OrderDetail">
<div class="order-item" @click="OrderDetail(item.id)" v-for="item in orders" :key="item.id">
<img class="order-img" src="@/assets/badge/suiji.png" alt="order1" />
<span class="order-status status-processing">生产中</span>
</div>
@@ -16,13 +16,48 @@
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { useRouter } from 'vue-router'
import * as badgeApi from '@/api/badge'
import { onMounted, ref } from 'vue'
const page = ref(1)
const loading = ref(false)
const finished = ref(false)
const orders = ref([])
onMounted(() => {
getOrder()
})
function getOrder() {
if(loading.value) return
loading.value = true
badgeApi.getMyOrder({
page: page.value,
pageSize: 10,
}).then(res => {
if(page.value === 1) {
orders.value = res.data.list
} else {
orders.value.push(...res.data.list)
}
if(res.data.list.length < 10) {
finished.value = true
} else {
page.value++
}
}).finally(() => {
loading.value = false
})
}
const router = useRouter();
function goBack() {
router.push('/badge');
}
function OrderDetail() {
router.push('/badge/orderDetail')
function OrderDetail(id: number) {
router.push({
path: '/badge/orderDetail',
query: {
id: id,
},
})
}
</script>
<style scoped>
+18 -2
View File
@@ -1,7 +1,7 @@
<template>
<div class="my-order">
<div class="header" @click="goBack">
<span class="back-icon"><van-icon name="arrow-left" size="16px" /> 返回</span>
<span class="back-icon"><van-icon name="arrow-left" size="16px" color="#333" /> 订单详情</span>
</div>
<div class="order-status">
<div class="avatar-wrapper">
@@ -68,8 +68,24 @@
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import * as badgeApi from '@/api/badge';
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const order = ref({})
const id = ref(0)
onMounted(() => {
id.value = route.query.id
getOrderDetail()
})
function getOrderDetail() {
badgeApi.getOrderDetail({
id: id.value,
}).then(res => {
order.value = res.data
})
}
const router = useRouter();
function goBack() {
router.push('/badge/myOrder');
+278 -17
View File
@@ -20,13 +20,16 @@
</div>
</div>
</div>
<div class="progress-section">
<div v-if="!imageUrl" class="progress-section">
<div class="progress-bar-bg">
<div class="progress-bar-fg" :style="{ width: '12%' }"></div>
<div class="progress-bar-fg" :style="{ width: progress + '%' }"></div>
</div>
<div class="progress-text">照片数据分析中 12%</div>
<div class="progress-text">照片数据分析中 {{progress}}%</div>
<div class="progress-desc">总计大约需要60秒,请耐心等待...</div>
</div>
<div v-else class="progress-section">
<img class="progress-section-img" :src="imageUrl" alt="">
</div>
<div class="info-section">
<div class="info-item">
<div class="info-title">芯比风格</div>
@@ -54,40 +57,230 @@
<span class="order-size">4cm</span>
<span class="order-free">(剩余兑换:1)</span>
<div class="order-ctrl">
<button class="order-btn" disabled>-</button>
<span class="order-count">1</span>
<button class="order-btn">+</button>
<van-stepper v-model="value" @change="changeValue" />
</div>
</div>
<div class="order-item">
<span class="order-size">5cm</span>
<span class="order-free">(剩余兑换:2)</span>
<div class="order-ctrl">
<button class="order-btn" disabled>-</button>
<span class="order-count">1</span>
<button class="order-btn">+</button>
<van-stepper v-model="value" />
</div>
</div>
</div>
<div class="confirm-box">
<div class="action-section">
<button class="action-btn"><img class="action-img" src="@/assets/badge/reload.png" alt=""> 再次生成</button>
<button class="action-btn"><img class="action-img" src="@/assets/badge/duibi.png" alt=""> 前后对比</button>
<button class="action-btn"><img class="action-img" src="@/assets/badge/down.png" alt=""> 保存图片</button>
<button @click="showReload = true" class="action-btn"><img class="action-img" src="@/assets/badge/reload.png" alt=""> 再次生成</button>
<button @click="compare" :disabled="flag < 2" class="action-btn"><img class="action-img" src="@/assets/badge/duibi.png" alt=""> 前后对比</button>
<button @click="save" :disabled="flag < 2" class="action-btn"><img class="action-img" src="@/assets/badge/down.png" alt=""> 保存图片</button>
</div>
<div class="btn-box">
<button class="confirm-btn">确认选择</button>
<button @click="confirm" :disabled="flag < 2" class="confirm-btn">确认选择</button>
</div>
</div>
</div>
<van-action-sheet v-model:show="showReload" title="风格样式选择" :close-on-click-overlay="true" closeable>
<div class="style-selection">
<div class="style-grid">
<div class="style-item" :class="{'active': style == 'suiji'}" @click="changeStyle('suiji')">
<img src="@/assets/badge/suiji.png" alt="随机风格盲盒" class="style-img">
<div class="style-label">随机风格盲盒</div>
<span class="style-selected" v-if="style == 'suiji'" style="display: inline-block;">
<van-icon name="checked" color="#50cf54" size="21px" />
</span>
</div>
<div class="style-item" :class="{'active': style == 'qban'}" @click="changeStyle('qban')">
<img src="@/assets/badge/suiji.png" alt="Q版国风动漫" class="style-img">
<div class="style-label">Q版国风动漫</div>
<span class="style-selected" v-if="style == 'qban'">
<van-icon name="checked" color="#50cf54" size="21px" />
</span>
</div>
<div class="style-item" :class="{'active': style == 'qkant'}" @click="changeStyle('qkant')">
<img src="@/assets/badge/suiji.png" alt="Q版卡通" class="style-img">
<div class="style-label">Q版卡通</div>
<span class="style-selected" v-if="style == 'qkant'">
<van-icon name="checked" color="#50cf54" size="21px" />
</span>
</div>
<div class="style-item" :class="{'active': style == 'babifeng'}" @click="changeStyle('babifeng')">
<img src="@/assets/badge/suiji.png" alt="芭比风" class="style-img">
<div class="style-label">芭比风</div>
<span class="style-selected" v-if="style == 'babifeng'">
<van-icon name="checked" color="#50cf54" size="21px" />
</span>
</div>
<div class="style-item">
<img src="@/assets/badge/suiji.png" alt="迪士尼皮克斯" class="style-img">
<div class="style-label">迪士尼皮克斯</div>
</div>
<div class="style-item">
<img src="@/assets/badge/suiji.png" alt="写实肖像" class="style-img">
<div class="style-label">写实肖像</div>
</div>
</div>
<div class="confirm-wrapper">
<button class="confirm-button">
确认再次生成
</button>
</div>
</div>
</van-action-sheet>
<van-action-sheet v-model:show="showCompare" title="前后对比" :close-on-click-overlay="true" closeable>
<div style="padding: 16px;">
<div style="display: flex; align-items: center; justify-content: center; gap: 20px;">
<div style="text-align: center;">
<img src="@/assets/badge/suiji.png" alt="随机风格盲盒" style="width: 120px; height: 120px; border-radius: 8px;">
</div>
<div style="font-size: 24px; color: #333;">
<van-icon name="arrow" size="20px" />
</div>
<div style="text-align: center;">
<img src="@/assets/badge/suiji.png" alt="随机风格盲盒" style="width: 120px; height: 120px; border-radius: 8px;">
</div>
</div>
<div style="text-align: center; margin-top: 16px; color: #999; font-size: 12px;">
温馨提示:<br/>
AI设计的徽章效果图是根据用户提供的照片机器学习生成的,<br/>
多试几次就能找到你满意的徽章~
</div>
</div>
</van-action-sheet>
<van-action-sheet v-model:show="showBgCompare" title="前后对比" :close-on-click-overlay="true" closeable>
<div style="padding: 16px;">
<div style="display: flex; align-items: center; justify-content: center; gap: 20px;">
<div style="text-align: center;">
<img src="@/assets/badge/suiji.png" alt="背景对比图" style="width: 120px; height: 120px; border-radius: 8px;">
</div>
<div style="font-size: 24px; color: #333;">
<van-icon name="plus" size="20px" />
</div>
<div style="text-align: center;">
<img src="@/assets/badge/suiji.png" alt="背景对比图" style="width: 120px; height: 120px; border-radius: 8px;">
</div>
</div>
<div style="font-size: 24px; color: #333;display: flex; align-items: center; justify-content: center; margin: 16px 0;">
<van-icon name="arrow-down" size="20px" />
</div>
<div style="display: flex; align-items: center; justify-content: center; gap: 20px;">
<div style="text-align: center;">
<img src="@/assets/badge/suiji.png" alt="背景对比图" style="width: 120px; height: 120px; border-radius: 8px;">
</div>
</div>
<div style="text-align: center; margin-top: 16px; color: #999; font-size: 12px;">
温馨提示:<br/>
AI设计的图案效果图是根据用户提供的照片机器学习生成的,<br/>
多试几次就能找到你满意的效果~
</div>
</div>
</van-action-sheet>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import * as badgeApi from '@/api/badge'
const router = useRouter();
const value = ref(0);
const showReload = ref(false)
const showCompare = ref(false)
const showBgCompare = ref(false)
function compare() {
// showCompare.value = true
showBgCompare.value = true
}
function save() {
// 下载图片到本地
const downloadImage = (url: string) => {
const link = document.createElement('a')
link.href = url
link.download = '徽章效果图.png'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
if (imageUrl.value) {
downloadImage(imageUrl.value)
} else {
showReload.value = true
}
}
const style = ref('suiji')
function changeStyle(style: string) {
console.log(style)
style.value = style
}
function confirm() {
console.log('confirm')
}
function goBack() {
router.push('/badge');
}
const changeValue = (value: number) => {
console.log(value)
}
const imageUrl = ref('')
const group = ref(1);
// 轮询获取图片
const flag = ref(1)
const timer = ref()
const progress = ref(0)
const progressTimer = ref()
const progressList = () => {
// 启动进度条计时器
progress.value = 0
if (!progressTimer.value) {
progressTimer.value = setInterval(() => {
if (progress.value < 90) {
progress.value += 1
}
}, 500)
}
}
const getImageList = () => {
badgeApi.getKindList({
pid: 0,
group: group.value
}).then((res: any) => {
const data = res || []
flag.value = data.flag || 1
if (flag.value === 2) {
// 清除轮询和进度条计时器
clearInterval(timer.value)
clearInterval(progressTimer.value)
// 进度条到100%
progress.value = 100
// 显示图片
imageUrl.value = data.url
}
}).catch((err) => {
clearInterval(progressTimer.value)
progress.value = 0
}).finally((err) => {
})
}
onMounted(() => {
getImageList()
progressList()
timer.value = setInterval(() => {
getImageList()
}, 10000)
})
onUnmounted(() => {
clearInterval(timer.value)
clearInterval(progressTimer.value)
})
</script>
<style scoped>
.preview-container {
@@ -214,6 +407,11 @@ function goBack() {
align-items: center;
flex-direction: column;
}
.progress-section-img {
width: 80vw;
height: 80vw;
border-radius: 12px;
}
.progress-bar-bg {
width: 70%;
height: 10px;
@@ -354,9 +552,6 @@ function goBack() {
height: 20px;
margin-right: 5px;
}
.action-btn:hover {
background: #e6f7ea;
}
.btn-box {
display: flex;
justify-content: center;
@@ -378,4 +573,70 @@ function goBack() {
.confirm-btn:active {
background: linear-gradient(90deg, #5ccf7a 0%, #8fdca0 100%);
}
.style-selection {
padding: 16px;
}
.style-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.style-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
position: relative;
width: 28vw;
}
.style-img {
width: 28vw;
height: 28vw;
border-radius: 8px;
object-fit: cover;
background: #f5f5f5;
border: 2px solid transparent;
transition: border 0.2s;
}
.style-item .style-selected {
position: absolute;
right: 4px;
bottom: 30px;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
}
.style-label {
font-size: 13px;
color: #333;
margin-top: 4px;
}
.confirm-wrapper {
margin-top: 24px;
}
.confirm-button {
width: 100%;
background: linear-gradient(90deg, #D1ED8E 0%, #55E668 100%);
color: #000;
font-size: 17px;
font-weight: 600;
border: none;
border-radius: 24px;
padding: 12px 0;
cursor: pointer;
}
.confirm-button:active {
background: linear-gradient(90deg, #5ccf7a 0%, #8fdca0 100%);
}
.style-item.active .style-img {
border: 2px solid #50cf54;
}
.style-item.active .style-label {
color: #50cf54;
}
</style>
+64 -44
View File
@@ -1,20 +1,22 @@
<template>
<div>
<div class="header" @click="goBack">
<span class="back-icon"><van-icon name="arrow-left" size="16px" /> 返回</span>
<div class="header" @click="goBack" style="text-align: center;">
<span class="back-icon"><van-icon name="arrow-left" size="16px" color="#333" /> 设计记录</span>
</div>
<div class="record-container">
<div class="record-note">
注:可点击效果图进行下单打印
</div>
<div class="record-note">注:可点击效果图进行下单打印</div>
<div class="record-images">
<div
v-for="(item, idx) in images"
:key="idx"
@click="onImageClick(item)"
class="record-image-item"
>
<img :src="item" class="record-image" />
<div class="record-image-item">
<img src="@/assets/badge/suiji.png" alt="效果图" style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%;">
</div>
<div class="record-image-item">
<img src="@/assets/badge/suiji.png" alt="效果图" style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%;">
</div>
<div class="record-image-item">
<img src="@/assets/badge/suiji.png" alt="效果图" style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%;">
</div>
<div class="record-image-item">
<img src="@/assets/badge/suiji.png" alt="效果图" style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%;">
</div>
</div>
</div>
@@ -22,6 +24,43 @@
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import * as badgeApi from '@/api/badge';
import { onMounted, ref } from 'vue';
const images = ref([]);
onMounted(() => {
getList();
});
const page = ref(1)
const loading = ref(false)
const finished = ref(false)
function getList() {
if(loading.value) return
loading.value = true
badgeApi.getLogOage({
page: page.value,
pageSize: 10,
}).then(res => {
if(page.value === 1) {
images.value = res.data.list
} else {
images.value.push(...res.data.list)
}
if(res.data.list.length < 10) {
finished.value = true
} else {
page.value++
}
}).finally(() => {
loading.value = false
})
}
function onLoad() {
getList()
}
const router = useRouter();
function goBack() {
router.push('/badge');
@@ -43,51 +82,32 @@ function goBack() {
cursor: pointer;
}
.record-container {
max-width: 500px;
margin: 24px auto;
padding: 16px;
background: #fff;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
}
.record-note {
color: #888;
font-size: 14px;
margin-bottom: 18px;
text-align: center;
font-size: 12px;
color: #999;
margin-bottom: 16px;
}
.record-images {
display: flex;
flex-wrap: wrap;
gap: 24px;
justify-content: center;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.record-image-item {
width: 160px;
height: 160px;
width: 100%;
padding-bottom: 100%;
position: relative;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
background: #f7f7f7;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: box-shadow 0.2s;
}
.record-image-item:hover {
box-shadow: 0 4px 16px rgba(0,0,0,0.14);
}
.record-image {
.record-image-item img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
display: block;
}
</style>
-29
View File
@@ -1,29 +0,0 @@
function googleToNormalize(data: any) {
return {
unionid: data.id,
social_type: 'google',
nickname: (data.last_name + data.first_name) || '',
email: data.email || '',
headimgurl: data.thumbnail || '',
}
}
function twitterToNormalize(data: any) {
return {
unionid: data.id_str || data.id,
social_type: 'twitter',
nickname: data.name || '',
email: data.email || '',
headimgurl: data.thumbnail || '',
}
}
export function normalize(socialType: string, data: any) {
switch (socialType) {
case 'google':
return googleToNormalize(data)
case 'twitter':
return twitterToNormalize(data)
}
}
-131
View File
@@ -1,131 +0,0 @@
<script setup lang="ts">
import hello from 'hellojs'
import type { Ref } from 'vue'
import { normalize } from './helpers/transfer'
// import { useUserStore } from '@/stores/user'
import router from '@/router'
import {useStore} from '@/stores'
import { useTokenStore } from '@/stores/token'
import * as authApi from '@/api/auth'
interface Item {
icon: string
name: string
text: string
}
const logins: Ref<Item[]> = ref([
// {
// icon: 'microsoft',
// name: 'windows',
// text: 'windows',
// },
{
icon: 'googleplus',
name: 'google',
text: 'G+',
},
// {
// icon: 'facebook',
// name: 'facebook',
// text: 'Facebook',
// },
// {
// icon: 'apple',
// name: 'apple',
// text: 'apple',
// },
{
icon: 'twitter',
name: 'twitter',
text: 'twitter',
},
// {
// icon: 'wechat',
// name: 'wechat',
// text: 'wechat',
// },
])
function onBtn(item: Item) {
switch (item.name) {
case 'wechat':
window.location.href = `https://wechat.api.suwa3d.com/api/auth/page?callback${encodeURIComponent(window.location.href)}`
return
}
hello(item.name).login().then((d: any) => {
console.log('d', d)
// hello(item.name).api('me').then((d: any) => {
// console.log('d', d)
// const data: any = normalize(item.name, d)
// // const user = useUserStore()
// // user.login(d)
// // useRouter().replace('/')
// }, (e: any) => {
// console.log('/me error', e)
// })
}, () => {
// console.log('login failed')
})
}
const stores = useStore();
hello.on('auth.login', (r: any) => {
hello(r.network).api('/me', 'get').then((d) => {
const data: any = normalize(r.network, d)
authApi.getUser(data).then((res: any) => {
useTokenStore().login(res)
let url = stores.redirectUrl();
if (url) {
stores.setRedirect('')
router.replace(url)
return
}
router.replace('/')
}, (e: any) => {
console.log('auth.login /me error', e)
})
})
})
// TODO: 这边需要进行配置
hello.init({
facebook: '1875721406193406',
windows: '000000004403AD10',
google: '495792315385-mj44iuov1bupumvl6evmmcvag39mi38t.apps.googleusercontent.com',
twitter: 'ICw3GtdIivmHPI5qTLD0gZDbN',
}, {
redirect_uri: './oauth.html',
scope: 'email',
})
</script>
<template>
<div class="container">
<van-divider>Social Login</van-divider>
<div v-for="(item, i) in logins" :key="i" class="l-btn">
<van-button plain icon-prefix="iconfont" :icon="item.icon" type="primary" size="large" @click="onBtn(item)">
{{ item.text }}
</van-button>
</div>
</div>
</template>
<style scoped lang="less">
.l-btn{
margin: .4rem;
width: 100%;
}
.container{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
}
</style>