Feat(送礼):支持AI的文生图

This commit is contained in:
lifizer
2026-09-08 15:23:29 +08:00
parent 8c00c82e3f
commit 733048e2c9
3 changed files with 579 additions and 13 deletions
+24
View File
@@ -124,3 +124,27 @@ export function computedSojoumGroupBuyGiftOrderPrice(params) {
login: true
})
}
/**
* 文生图发起生成
* @param {*} data
* @returns
*/
export function aiGiftImageGenerate(data) {
return request.post("/aiGiftImage/generate", data, {
login: true
})
}
/**
* 文生图状态查询
* @param {*} params
* @returns
*/
export function aiGiftImageStatus(params) {
return request.get("/aiGiftImage/" + params.productId, {
login: true
})
}
@@ -0,0 +1,155 @@
<template>
<u-popup
:show="show"
mode="bottom"
round="40rpx"
:customStyle="{height: '600rpx'}"
@close="close"
>
<view class="cover-picker v12-pt-4">
<swiper
:current="currentIndex"
:autoplay="false"
:circular="true"
:indicator-dots="false"
@change="onChange"
previous-margin="180rpx"
next-margin="180rpx"
class="cover-swiper"
>
<swiper-item v-for="(item, i) in list" :key="i" class="cover-swiper-item" :class="{'n-actived' : currentIndex !== i}">
<view class="cover-item" :class="{'ios-cover': isIOS}">
<view class="img-wrap">
<image :src="item.cover" class="cover-image" mode="widthFix" />
</view>
</view>
</swiper-item>
</swiper>
<view class="v12-justify-center">
<view class="cover-confirm" @click="confirm">
确定
</view>
</view>
<view class="tips">礼包封面在对方领取时展示</view>
</view>
</u-popup>
</template>
<script>
export default {
props: {
index: {
type: Number,
default: 0
},
show: {
type: Boolean,
default: true
},
list: {
type: Array,
default: () => []
}
},
data() {
return {
currentIndex: this.index
}
},
computed: {
isIOS() {
return uni.getSystemInfoSync().osName === 'ios'
}
},
watch: {
index(val) {
this.currentIndex = val
}
},
methods: {
close() {
this.currentIndex = 0
this.$emit('update:show', false)
},
onChange(e) {
this.currentIndex = e.detail.current
this.$forceUpdate()
},
confirm() {
this.$emit('confirm', this.list[this.currentIndex])
this.$emit('update:show', false)
}
}
}
</script>
<style lang="less" scoped>
.n-actived{
.cover-item{
transform: scale(0.8);
margin-bottom: -70rpx !important;
}
}
.cover-swiper-item{
height: fit-content !important;
bottom: 0;
overflow: visible;
}
.img-wrap{
position: relative;
overflow: visible;
}
.tips{
font-size: 24rpx;
color: #999;
text-align: center;
margin-top: 20rpx;
}
.cover-swiper{
height: 800rpx;
}
.cover-picker {
.cover-title {
padding: 30rpx;
font-size: 32rpx;
font-weight: bold;
text-align: center;
margin-bottom: 30rpx;
}
.cover-item {
display: flex;
flex-direction: column;
align-items: center;
overflow: visible;
transition: all .3s ease-in-out;
margin-bottom: 30rpx;
.cover-image {
width: 420rpx;
// height: 580rpx;
border-radius: 16rpx;
transition: all .2s ease-in-out;
}
.cover-name {
margin-top: 20rpx;
font-size: 28rpx;
color: #333;
}
}
.cover-confirm {
margin-top: 40rpx;
width: 240rpx;
height: 80rpx;
border-radius: 16rpx;
font-size: 32rpx;
background-color: #C52733 !important;
color: #fff !important;
text-align: center;
line-height: 80rpx;
}
}
.ios-cover{
zoom: 1.01
}
</style>
+400 -13
View File
@@ -1,9 +1,81 @@
<template>
<view class="gift-page" v-if="(goodsInfo.productInfo && goodsInfo.productInfo.storeName) || isGroupBuy">
<view class="top-card" v-if="!isPayOk ">
<view class="v12-justify-between" @click="handleShowCover">
<view class="top-card" v-if="!isPayOk">
<view class="top-tab-nav-wrap">
<view
:class="{'active': selectCoverType === 'default'}"
class="item item1"
@click="toggleSelectCoverType('default')"
>
<view class="txt">选择礼包封面</view>
<image :src="webUrl + '/page/gift/gift-tab01.png'" class="tab-img"></image>
</view>
<view
:class="{'active': selectCoverType === 'ai'}"
class="item item2"
@click="toggleSelectCoverType('ai')"
>
<view class="txt">AI生成礼包封面</view>
<image :src="webUrl + '/page/gift/gift-tab02.png'" class="tab-img"></image>
</view>
</view>
<view
v-if="selectCoverType === 'default'"
class="v12-justify-between"
style="margin-top: -1px;"
@click="handleShowCover"
>
<image class="popCover" :src="popupCover" mode="widthFix" lazy-load="false"></image>
</view>
<view
v-if="selectCoverType === 'ai'"
class="ai-cover-wrap"
>
<view class="ai-cover-body">
<view class="ai-cover-container">
<view v-if="showGenerateBtn" class="prompt">
<textarea
v-model.trim="aiCoverDesc"
placeholder="请描述你想生成的图片"
class="prompt-textarea"
/>
</view>
<view v-else class="imgs-list-wrap">
<view class="img-item" v-for="(item, index) in aiCoverImgsList" :key="index">
<image
:src="item.cover"
:class="{
'active': item.id === selectedAiCoverImage.id
}"
mode="heightFix"
class="img-image"
/>
</view>
</view>
</view>
<view class="ai-cover-bottom">
<view v-if="showGenerateBtn" class="btn-wrap">
<view class="btn" @click="handleGenerateAiCover">
生成礼包封面
<image :src="webUrl + '/page/gift/gift-tab-icon01.png'" class="icon"></image>
</view>
<image
v-if="aiCoverGenerateLoading && settingInfo.giftCardT2ILoadingGif"
:src="settingInfo.giftCardT2ILoadingGif"
class="loading-img"
/>
</view>
<view v-else class="btn-wrap">
<view class="btn" @click="handleShowAiCover">
预览
</view>
<view class="btn btn2" @click="handleGenerateAgain">
重新生成
</view>
</view>
</view>
</view>
</view>
</view>
<view class="gift-good-card">
<view class="v12-align-center v12-justify-start">
@@ -130,7 +202,7 @@
</view>
</button>
<view class="btn-wrap v12-mt-12 v12-justify-center" v-if="!isPayOk">
<view class="gift-submit" @click="showPayPicker = true">付款并赠送</view>
<view class="gift-submit" @click="payNowToSendGift">付款并赠送</view>
</view>
<view class="btn-wrap v12-mt-14 v12-justify-center" v-if="isPayOk && !isGroupBuy">
<view class="gift-submit" @click="makeAgain">再送一份</view>
@@ -148,6 +220,12 @@
:index="defaultIndex"
:message="message"
/>
<AiCoverPicker
:show.sync="showAiCoverPicker"
:list="aiCoverImgsList"
:index="aiDefaultIndex"
@confirm="onAiCoverConfirm"
/>
<passkeyborad
ref='payPwdPop'
showMoney
@@ -216,18 +294,25 @@ import {
createGroupBuyGift,
createSojoumGroupBuyGift,
fetchSojoumGroupBuyGiftCarousel,
computedSojoumGroupBuyGiftOrderPrice
computedSojoumGroupBuyGiftOrderPrice,
aiGiftImageGenerate,
aiGiftImageStatus
} from '@/api/gift'
import {
getAiSystemInfoSetting
} from '@/api/public'
import { weappPay } from "@/libs/wechat";
import {mapGetters} from "vuex";
import giftTips from './components/giftTips.vue'
import CoverPicker from './components/CoverPicker.vue'
import AiCoverPicker from './components/AiCoverPicker.vue'
import PayPicker from './components/PayPicker.vue';
import CouponsPopup from "@/components/CouponsPopup.vue"
export default {
components: {
giftTips,
CoverPicker,
AiCoverPicker,
PayPicker,
passkeyborad,
CouponsPopup
@@ -236,8 +321,11 @@ export default {
return {
webUrl: this.$VUE_APP_RESOURCES_URL,
loading: true,
// 盲盒礼包
isBlind: false,
// 一人领取所有礼品
isOneRecipient: false,
// 是否支持好友转赠
canTransfer: false,
message: '',
cartId: '',
@@ -258,6 +346,7 @@ export default {
isShowPayPwdPop: false,
payPassword: '',
payType: '',
// 是否支付成功
isPayOk: false,
giftKey: '',
payPrice: 0,
@@ -271,13 +360,46 @@ export default {
},
couponTitle: '',
showPopup: false,
// 盲盒礼包图片
blindImage: '',
// 是否为旅居团购礼品
isGroupBuy: false,
popupCover: '',
showInput: true,
// 是否为县区抽奖礼品
isCountyLotteryGift: false,
// 县域抽奖弹窗
showLotteryGiftPopup: false,
isEligibleForDraw: false
isEligibleForDraw: false,
// 封面切换: default/ai
selectCoverType: 'default',
aiCoverImgsList: [
/*
{
id: 15,
cover: 'https://resource.xdd618.com/1/20251202/1764661118796_中秋 2礼品卡1202.png',
image: 'https://resource.xdd618.com/1/20251202/1764661118797_中秋2 对话框封面.png'
},
{
id: 23,
cover: 'https://resource.xdd618.com/1/20251202/1764661193306_中秋 3礼品卡1202.png',
image: 'https://resource.xdd618.com/1/20251202/1764661193306_中秋3 对话框封面.png'
},
{
id: 41,
cover: 'https://resource.xdd618.com/1/20251202/1764661506015_特色好礼礼品卡1202.png',
image: 'https://resource.xdd618.com/1/20250902/1756781423380_特色好礼对话框封面定稿.png'
}
*/
],
showGenerateBtn: true,
aiCoverGenerateLoading: false,
settingInfo: {},
aiCoverDesc: '',
selectedAiCoverImage: {},
showAiCoverPicker: false,
pollInterval: null,
aiDefaultIndex: 0
}
},
watch: {
@@ -313,6 +435,16 @@ export default {
fetchSojoumGroupBuyGiftCarousel().then(res => {
this.popupCover = res.data[0]
})
getAiSystemInfoSetting().then(res => {
const { success, data } = res
if (success) {
this.settingInfo = data
this.aiCoverDesc = data.giftCardT2IPromptTemplate || ''
}
})
},
onUnload() {
this.clearIntervalPoll()
},
computed: {
...mapGetters(["userInfo"]),
@@ -333,6 +465,95 @@ export default {
}
},
methods: {
// 將面切换
toggleSelectCoverType(type) {
if (this.selectCoverType === type) return
this.selectCoverType = type
},
handleGenerateAiCover() {
if (!this.aiCoverDesc || !this.aiCoverDesc.trim()) {
uni.showToast({
title: '请先配置文生图提示词',
icon: 'none'
})
return
}
if (this.aiCoverGenerateLoading) return
if (this.pollInterval) return
// 点击生成后,禁用按钮,防止重复点击
this.aiCoverGenerateLoading = true
aiGiftImageGenerate({
prompt: this.aiCoverDesc,
productId: this.goodsInfo.productId
}).then(res => {
const { success, data } = res
if (success) {
this.aiDefaultIndex = 0
this.selectedAiCoverImage = {}
this.aiCoverImgsList = []
// 轮询查询状态
this.pollAiImageGenerateStatus()
}
}).catch(() => {
this.aiCoverGenerateLoading = false
})
},
pollAiImageGenerateStatus() {
const _this = this
this.pollInterval = setInterval(() => {
aiGiftImageStatus({
productId: _this.goodsInfo.productId
}).then(res => {
const { success, data } = res
if (success && data) {
// status: 0=生成中 / 1=成功 / 2=失败(无任何记录时为 null)
if (data.status === 1 || data.status === 2) {
_this.clearIntervalPoll()
}
if (data.status === 1 && data.groups && data.groups.length) {
_this.selectedAiCoverImage = data.groups[0]
_this.aiCoverImgsList = []
data.groups.map(item => {
_this.aiCoverImgsList.push({
id: item.id,
cover: item.coverUrl,
image: item.shareUrl,
name: item.name || ''
})
})
_this.selectedAiCoverImage = _this.aiCoverImgsList[0]
// 封面图片
_this.selectedCover = _this.selectedAiCoverImage.cover
// 分享对话图片
_this.giftImage = _this.selectedAiCoverImage.image
console.log(_this.selectedCover)
console.log(_this.giftImage)
_this.showGenerateBtn = false
_this.aiCoverGenerateLoading = false
}
} else {
_this.aiCoverGenerateLoading = false
_this.showGenerateBtn = true
}
}).catch(() => {
_this.aiCoverGenerateLoading = false
_this.showGenerateBtn = true
})
}, 2000)
},
clearIntervalPoll() {
if (this.pollInterval) {
clearInterval(this.pollInterval)
this.pollInterval = null
}
},
handleGenerateAgain() {
this.aiDefaultIndex = 0
this.selectedAiCoverImage = {}
this.aiCoverImgsList = []
this.showGenerateBtn = true
this.aiCoverGenerateLoading = false
},
resolvePageQuery() {
if (this.$yroute && this.$yroute.query) {
return this.$yroute.query
@@ -418,6 +639,9 @@ export default {
handleShowCover() {
this.showCoverPicker = true
},
handleShowAiCover() {
this.showAiCoverPicker = true
},
showCouponsPopup() {
if (this.isCountyLotteryGift) return
if (this.couponList.usable && this.couponList.usable.length === 0) return
@@ -472,6 +696,11 @@ export default {
console.log(this.defaultIndex, 'defaultIndex');
this.selectedCover = template.cover
this.giftImage = template.image
if (this.selectCoverType === 'ai') {
this.selectedCover = this.selectedAiCoverImage.cover
this.giftImage = this.selectedAiCoverImage.image
this.templateId = this.selectedAiCoverImage.id
}
this.couponId = ''
this.computedPrice(this.goodsInfo.cartNum)
})
@@ -520,6 +749,24 @@ export default {
this.$forceUpdate()
})
},
payNowToSendGift() {
if (this.selectCoverType === 'default') {
this.showPayPicker = true
return
}
if (this.selectCoverType === 'ai' && !this.selectedAiCoverImage.id) {
if (!this.selectedAiCoverImage.id) {
uni.showToast({
title: '请生成礼包封面再操作',
icon: 'none'
})
return
} else {
this.showPayPicker = true
}
return
}
},
pwdPopFinish(payPwd) {
this.payPassword = payPwd;
this.confirmGift(true);
@@ -531,9 +778,21 @@ export default {
onCoverConfirm(cover) {
this.cover = cover.name
this.templateId = cover.id
// 封面图片
this.selectedCover = cover.cover
// 分享对话图片
this.giftImage = cover.image
},
onAiCoverConfirm(cover) {
// 封面图片
this.selectedCover = cover.cover
// 分享对话图片
this.giftImage = cover.image
if (this.selectCoverType === 'ai') {
this.selectedAiCoverImage = cover
this.aiDefaultIndex = this.aiCoverImgsList.findIndex(item => item.id === cover.id)
}
},
showTips() {
this.$refs.giftTips.showTips = true
},
@@ -616,7 +875,9 @@ export default {
key: this.orderKey,
from: 'routine',
cartId: this.cartId,
templateId: this.templateId,
templateId: this.selectCoverType === 'default' ? this.templateId : '',
aiGiftImageId: this.selectCoverType === 'default' ? '' : this.selectedAiCoverImage.id,
cover: this.selectedCover,
message : this.message || '礼笺轻启,礼藏心意',
canTransfer: this.canTransfer ? 1 : 0,
passwordPay: this.payPassword || '',
@@ -628,7 +889,8 @@ export default {
cartId: this.cartId,
couponId: this.getCouponId(),
recipientsNum: this.goodsInfo.cartNum,
templateId: this.templateId,
templateId: this.selectCoverType === 'default' ? this.templateId : '',
aiGiftImageId: this.selectCoverType === 'default' ? '' : this.selectedAiCoverImage.id,
message : this.message || '礼笺轻启,礼藏心意',
cover: this.selectedCover,
canTransfer: this.canTransfer ? 1 : 0,
@@ -814,11 +1076,7 @@ export default {
border-radius: 16rpx;
}
.tip-wrap{
position: absolute;
bottom: 50rpx;
left: 0;
margin: auto;
right: 0;
margin: 32rpx auto 68rpx auto;
align-items: center;
}
.tip-text{
@@ -844,7 +1102,6 @@ export default {
}
.top-card{
// height: 280rpx;
background: #fff;
border-radius: 16rpx;
}
.line{
@@ -973,4 +1230,134 @@ export default {
.lottery-gift-btn.send::after{
border: none;
}
.top-tab-nav-wrap {
display: flex;
justify-content: space-between;
align-items: center;
.item1 {
.txt {
left: -24rpx;
}
}
.item2 {
.txt {
padding-left: 32rpx;
}
}
.item {
position: relative;
flex: 1;
text-align: center;
font-size: 32rpx;
color: #333;
line-height: 80rpx;
&.active {
color: #fff;
.tab-img {
display: block;
}
}
.txt {
position: relative;
z-index: 5;
}
.tab-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 80rpx;
display: none;
}
}
}
.ai-cover-wrap {
padding: 20rpx;
margin-top: -1px;
box-sizing: border-box;
border-radius: 28rpx 0 28rpx 28rpx;
background-color: #C52932;
.ai-cover-body {
padding: 20rpx;
box-sizing: border-box;
border-radius: 40rpx;
background-color: #fff;
}
.ai-cover-container {
.prompt {
.prompt-textarea {
width: 100%;
height: 200rpx;
background: #fff;
border-radius: 16rpx;
padding: 0 20rpx 20rpx 20rpx;
box-sizing: border-box;
overflow-y: auto;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
}
}
.imgs-list-wrap {
display: flex;
justify-content: space-around;
align-items: baseline;
gap: 24rpx;
padding: 20rpx 0;
box-sizing: border-box;
}
.img-image {
display: block;
height: 176rpx;
&.active {
transform: scale(1.2);
}
}
}
.ai-cover-bottom {
margin-top: 12rpx;
.btn-wrap {
display: flex;
justify-content: center;
align-items: center;
.loading-img {
display: block;
width: 48rpx;
height: 48rpx;
margin-left: 20rpx;
}
}
.btn + .btn {
margin-left: 20rpx;
}
.btn {
display: flex;
justify-content: center;
align-items: center;
height: 54rpx;
line-height: 54rpx;
border-radius: 40rpx;
text-align: center;
font-size: 32rpx;
font-weight: 500;
box-sizing: border-box;
margin: 0;
padding: 0 32rpx;
border: 2rpx solid #c52733;
color: #fff;
background: #c52733;
.icon {
display: block;
width: 28rpx;
height: 28rpx;
margin-left: 16rpx;
}
}
.btn2 {
background: #fff;
color: #c52733;
border: 2rpx solid #c52733;
}
}
}
</style>