Merge branch 'dev' into test

This commit is contained in:
lifizer
2026-09-15 09:14:05 +08:00
20 changed files with 926 additions and 145 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
})
}
+8
View File
@@ -603,3 +603,11 @@ export function queryUserGuide(params) {
export function setUserGuide(params) {
return request.post('/user/guide/status', params)
}
/**
* 提现规则
*/
export function getWithdrawalRules() {
return request.get('/extract/rule')
}
+4 -5
View File
@@ -35,8 +35,7 @@
v-for="(subItem, subIndex) in item.attrValue"
:key="subIndex"
:class="{
'actived': subItem.check,
'disabled': !subItem.canUsed
'actived': subItem.check
}"
class="itemn v12-radius-40"
@click="tapAttr(index, subIndex, subItem)"
@@ -218,9 +217,9 @@ export default {
},
tapAttr(index, subIndex, subItem) {
// 缺货的点击了没效果
if (!subItem.canUsed || subItem.check) {
return
}
// if (!subItem.canUsed || subItem.check) {
// return
// }
this.attrObj.productAttr[index].attrValue.map((subItem, subIdx) => {
subItem.check= false
if (subIndex === subIdx) {
+26 -4
View File
@@ -41,7 +41,9 @@
</view>
</view>
<view class="v4-nodata" v-show="!hasList">
<view class="loading-more" v-show="isWait">加载中...</view>
<view class="v4-nodata" v-show="showEmpty">
<image src="@/static/images/img_nodata.png" mode="scaleToFill"/>
<view class="text">暂无数据</view>
</view>
@@ -73,12 +75,16 @@ export default {
leftHeight: 0,
rightHeight: 0,
isWait: false,
requestId: 0,
pageKeyId: Object.freeze('findVideoList')
}
},
computed: {
hasList() {
return this.leftList.length + this.rightList.length > 0
},
showEmpty() {
return !this.isWait && !this.hasList
}
},
onLoad(options) {
@@ -106,7 +112,9 @@ export default {
changeType(item) {
this.type = item.value
this.page = 1
this.resetList()
// 使进行中的请求失效并解锁,避免切换时被上一次请求阻塞
this.requestId++
this.isWait = false
this.fetchList()
},
resetList() {
@@ -117,6 +125,7 @@ export default {
},
async fetchList() {
if (this.isWait) return
const requestId = ++this.requestId
this.isWait = true
const params = {
type: this.type,
@@ -125,13 +134,17 @@ export default {
}
try {
const res = await getVideoList(params)
// 请求已被更新的请求(如切换分类)取代,丢弃本次结果
if (requestId !== this.requestId) return
if (res.status === 200) {
const items = res.data
for (let i = 0; i < items.length; i++) {
items[i].hide = true
}
// 并行获取所有封面高度
// 并行获取所有封面高度,完成后一次性替换列表,避免闪现空列表
const heights = await Promise.all(items.map(item => this.getImageHeight(item.cover)))
if (requestId !== this.requestId) return
if (this.page === 1) this.resetList()
items.forEach((item, i) => {
this.pushToColumn(item, heights[i] + TEXT_HEIGHT)
})
@@ -139,7 +152,9 @@ export default {
} catch (e) {
// 忽略请求或图片尺寸获取失败
} finally {
this.isWait = false
if (requestId === this.requestId) {
this.isWait = false
}
}
},
getImageHeight(src) {
@@ -252,4 +267,11 @@ export default {
}
}
}
.loading-more {
padding: 24rpx 0;
text-align: center;
font-size: 24rpx;
color: #999;
}
</style>
+5 -1
View File
@@ -114,7 +114,8 @@
<view class="icon-box"><u-icon name="arrow-rightward" color="#fff" size="8"></u-icon></view>
</view>
</view>
<view class="v4-nodata" v-if="townList.length === 0">
<goo-skeleton v-if="!townLoaded" :show-avatar="false"/>
<view class="v4-nodata" v-else-if="townList.length === 0">
<image src="@/static/images/img_nodata.png" mode="scaleToFill"/>
<view class="text">暂无数据</view>
</view>
@@ -203,6 +204,7 @@ export default {
isNext: false,
townConfig: {},
townList: [],
townLoaded: false,
randomSeed: null,
hotelBanner: [],
goodCityId: '',
@@ -519,6 +521,8 @@ export default {
}
getAncientTownList(params).then(res => {
this.townList = res.data.records
}).finally(() => {
this.townLoaded = true
})
return
}
+3 -1
View File
@@ -309,7 +309,7 @@
</view>
</view>
<!-- v6服务项目结束 -->
<view class="store-section goods-section" v-if="systemStore">
<view class="store-section goods-section" v-if="systemStore && appProductHideMerchant !== '1'">
<view class="flex jc-between ai-center">
<view class="title bold flex ai-center">
<view>
@@ -579,6 +579,7 @@ export default {
mixins: [pageListenMixins],
data: function () {
return {
appProductHideMerchant: '0',
isGift: false,
giftBg: '',
// 商品信息是否展开
@@ -1210,6 +1211,7 @@ export default {
this.posterData.price = this.storeInfo.price
this.posterData.code = this.storeInfo.codeBase
this.systemStore = data.systemStore
this.appProductHideMerchant = data.appProductHideMerchant
const good_list = data.goodList || []
const goodArray = []
const count = Math.ceil(good_list.length / 6)
+12 -10
View File
@@ -317,7 +317,7 @@
</view>
</view>
<u-divider></u-divider>
<view class="v12-justify-between v12-align-center v12-mt-3" v-if="orderInfo.isGiftCardReceiveBlind === 0">
<view class="v12-justify-between v12-align-center v12-mt-3" v-if="orderInfo.isGiftCardReceiveBlind === 0 && !isCustomGiftOrder(orderInfo)">
<view class="v12-font-28 v12-font-bold v12-dark-text">
实付款
</view>
@@ -498,15 +498,17 @@
<text class="v12-font-22 v12-dark1-text">
{{ orderInfo.totalNum }}件商品
</text>
<text class="v12-font-bold v12-dark-text v12-font-28 v12-mx-2">
实付款
</text>
<text class="v12-font-bold v12-primary-text v12-font-22">
</text>
<text class="v12-font-bold v12-primary-text v12-font-28">
{{ force2Decimal(orderInfo.payPrice) }}
</text>
<template v-if="!isCustomGiftOrder(orderInfo)">
<text class="v12-font-bold v12-dark-text v12-font-28 v12-mx-2">
实付款
</text>
<text class="v12-font-bold v12-primary-text v12-font-22">
</text>
<text class="v12-font-bold v12-primary-text v12-font-28">
{{ force2Decimal(orderInfo.payPrice) }}
</text>
</template>
</view>
<view class="bnt v12-primary-text v12-primary-border v12-ml-1" @click="subscribePay">立即付款</view>
</view>
+2 -2
View File
@@ -23,7 +23,7 @@
<view class="item acea-row row-between-wrapper">
<view class="name">提现</view>
<view class="input">
<input :placeholder="'最低提现金额' + minPrice" v-model="post.money" />
<input v-model="post.money" />
</view>
</view>
<view class="tip">当前可提现金额: {{ commissionCount }}</view>
@@ -45,7 +45,7 @@
<view class="item acea-row row-between-wrapper">
<view class="name">提现</view>
<view class="input">
<input :placeholder="'最低提现金额' + minPrice" v-model="post.money" />
<input placeholder="请输入提现金额" v-model="post.money" />
</view>
</view>
<view class="tip">当前可提现金额: {{ commissionCount }}</view>
+90 -37
View File
@@ -108,8 +108,11 @@ export default {
uploadToken: null, //上传token
socketOpen: false,
socketConnecting: false, // 是否正在建连
socketTask: null,
socketMsgQueue: [],
hasConnected: false, // 是否成功建过连接
reconnectHistory: false, // 重连后用服务端历史整体替换本地列表,防止消息重复
roomId: null, //房间ID
content: "", //消息内容
@@ -136,30 +139,22 @@ export default {
},
onShow() {
this.content = uni.getStorageSync(this.merName+this.uid+this.storeId)
this.connectSocketInit();
this.list = []
// 已连接/连接中不重复建连;清空 list 会导致图片操作(触发 onHide/onShow)后页面白屏
if (!this.socketOpen && !this.socketConnecting) {
this.connectSocketInit();
}
},
onUnload() {
if (this.socketOpen) {
this.socketTask.close();
}
uni.onSocketClose(res => {
console.log('WebSocket 已关闭!', res);
});
this.closeSocket();
uni.setStorageSync(this.merName+this.uid+this.storeId, this.content)
},
onHide() {
if (this.socketOpen) {
this.socketTask.close();
}
uni.onSocketClose(res => {
console.log('WebSocket 已关闭!', res);
});
this.closeSocket();
uni.setStorageSync(this.merName+this.uid+this.storeId, this.content)
},
methods: {
toGoods(item) {
const itemJson = JSON.parse(item.msg_content)
const itemJson = this.wsJson(item.msg_content)
if(!itemJson.goodsId) {
// 提示数据错误
// uni.showToast({
@@ -177,7 +172,12 @@ export default {
})
},
wsJson(json) {
return JSON.parse(json)
// 兼容脏数据,避免渲染时报错导致白屏
try {
return JSON.parse(json) || {}
} catch (e) {
return {}
}
},
// 拼接图片完整地址:七牛上传后消息里只存 key,需要补上资源域名;http 统一转 https
imgUrl(content) {
@@ -255,33 +255,68 @@ export default {
});
},
// 进入这个页面的时候创建websocket连接【整个页面随时使用】
// 进入页面时创建websocket连接【整个页面随时使用】
connectSocketInit() {
if (this.socketOpen || this.socketConnecting) return;
this.socketConnecting = true;
// 创建一个this.socketTask对象【发送、接收、关闭socket都由这个对象操作】
this.socketTask = uni.connectSocket({
const task = uni.connectSocket({
url: this.$SOCKET_URL,
success: () => {
}
})
if (!task) {
this.socketConnecting = false;
return;
}
this.socketTask = task;
uni.onSocketOpen(res => {
console.log('WebSocket连接已打开!', res);
// 任务级回调:全局 uni.onSocket* 每次进页面都会重复注册,导致消息重复处理、连接串台
const isCurrent = () => this.socketTask === task;
task.onOpen(() => {
if (!isCurrent()) return;
this.socketOpen = true;
this.socketConnecting = false;
// 断线重连后重新加入房间;本地已有历史时用服务端历史整体替换,避免重复
this.reconnectHistory = this.hasConnected && this.list.length > 0;
this.hasConnected = true;
this.createRoom();
// 补发连接未就绪期间排队的消息(如发图片时连接被隐藏页关闭)
if (this.socketMsgQueue.length) {
const queue = this.socketMsgQueue.splice(0);
queue.forEach(msg => this.sendSocketMessage(msg));
}
});
uni.onSocketError(err => {
console.log('WebSocket连接打开失败,请检查!', err);
});
uni.onSocketMessage(res => {
console.log('收到服务器内容:', res.data);
task.onMessage(res => {
if (!isCurrent()) return;
this.handleMessage(res.data);
});
uni.onSocketClose(res => {
console.log('WebSocket 已关闭', res);
task.onError(err => {
console.log('WebSocket连接打开失败,请检查', err);
if (!isCurrent()) return;
this.socketOpen = false;
this.socketConnecting = false;
});
task.onClose(() => {
if (!isCurrent()) return;
this.socketOpen = false;
this.socketConnecting = false;
});
},
closeSocket() {
this.socketOpen = false;
this.socketConnecting = false;
if (this.socketTask) {
try {
this.socketTask.close({});
} catch (e) {}
}
},
userInput(event) {
@@ -321,11 +356,14 @@ export default {
},
sendSocketMessage(msg) {
if (this.socketOpen) {
uni.sendSocketMessage({
if (this.socketOpen && this.socketTask) {
this.socketTask.send({
data: msg,
success(res) {
console.log("消息发送成功", res,msg);
fail: () => {
// 发送失败说明连接可能已断开,重新排队并尝试重连后补发
this.socketMsgQueue.push(msg);
this.closeSocket();
this.connectSocketInit();
}
});
} else {
@@ -334,7 +372,12 @@ export default {
},
handleMessage(str) {
let data = JSON.parse(str);
let data;
try {
data = JSON.parse(str);
} catch (e) {
return;
}
switch (data.type) {
case "room_id":
this.roomId = data.msg_content;
@@ -344,13 +387,23 @@ export default {
case "leave":
break;
case "msg":
this.list.push(data.msg_content);
this.scrollLast();
if (data.msg_content) {
this.list.push(data.msg_content);
this.scrollLast();
}
break;
case "msg_history":
this.list = data.msg_content.concat(this.list);
case "msg_history": {
const history = data.msg_content || [];
if (this.reconnectHistory) {
// 重连后服务端重发历史:整体替换,避免消息重复
this.reconnectHistory = false;
this.list = history;
} else {
this.list = history.concat(this.list);
}
this.scrollLast();
break;
}
default:
break;
}
+15 -17
View File
@@ -6,7 +6,7 @@
labelWidth="216rpx"
>
<view
v-if="stateTitle && isDraft !== 1"
v-if="stateTitle"
class="box-state"
>
<u-alert
@@ -744,22 +744,20 @@ export default {
this.show = true
}
}
if (data.isDraft !== 1) {
switch (data.reviewStatus) {
case 0:
this.stateTitle = '审核中'
this.stateType = 'primary'
this.reviewing = true
break
case 1:
this.reviewing = false
break
case 2:
this.stateTitle = '审核驳回:' + data.reviewMsg
this.stateType = 'error'
this.reviewing = false
break
}
switch (data.reviewStatus) {
case 0:
this.stateTitle = '审核中'
this.stateType = 'primary'
this.reviewing = true
break
case 1:
this.reviewing = false
break
case 2:
this.stateTitle = '审核驳回:' + data.reviewMsg
this.stateType = 'error'
this.reviewing = false
break
}
if (data.hotelInfo) {
// 店铺图片
+15 -17
View File
@@ -7,7 +7,7 @@
labelWidth="216rpx"
>
<view
v-if="stateTitle && isDraft !== 1"
v-if="stateTitle"
class="box-state"
>
<u-alert
@@ -921,22 +921,20 @@ export default {
if(data.isDraft === null || data.isDraft === 1) {
this.show = true
}
if (data.isDraft !== 1) {
switch (data.reviewStatus) {
case 0:
this.stateTitle = '审核中'
this.stateType = 'primary'
this.reviewing = true
break
case 1:
this.reviewing = false
break
case 2:
this.stateTitle = '审核驳回:' + data.reviewMsg
this.stateType = 'error'
this.reviewing = false
break
}
switch (data.reviewStatus) {
case 0:
this.stateTitle = '审核中'
this.stateType = 'primary'
this.reviewing = true
break
case 1:
this.reviewing = false
break
case 2:
this.stateTitle = '审核驳回:' + data.reviewMsg
this.stateType = 'error'
this.reviewing = false
break
}
if (data.hotelInfo) {
// 店铺图片
+13 -5
View File
@@ -592,7 +592,11 @@
{{ tab.name }}
</view>
</view>
<view class="village-wrap v12-pt-3">
<scroll-view
class="village-wrap v12-pt-3"
scroll-y
@scrolltolower="getShopList()"
>
<view class="v12-pa-2 sale-wrap">
<view
v-for="(type, typeIndex) in shopTypeList"
@@ -645,7 +649,7 @@
</view>
</view>
<view v-else class="no-data">暂无店铺</view>
</view>
</scroll-view>
</view>
</view>
<view v-if="shopList.length > 0" class="load-more-status">
@@ -670,9 +674,13 @@
{{ tab.name }}
</view>
</view>
<view class="village-wrap">
<scroll-view
class="village-wrap"
scroll-y
@scrolltolower="getJiaLouListReq()"
>
<view class="v12-pa-2 sale-wrap">
</view>
<view v-if="jiaLouList.length > 0" class="village-list">
<view
@@ -713,7 +721,7 @@
</view>
</view>
<view v-else class="no-data">暂无数据</view>
</view>
</scroll-view>
</view>
</view>
<view v-if="jiaLouList.length > 0" class="load-more-status">
+8 -4
View File
@@ -333,7 +333,8 @@ export default {
this.$yrouter.replace({
path: "/pages/order/MyOrder/index",
query: {
type: 1
type: 1,
tabIndex: 1
}
});
break;
@@ -355,7 +356,8 @@ export default {
this.$yrouter.replace({
path: "/pages/order/MyOrder/index",
query: {
type: 1
type: 1,
tabIndex: 1
}
})
break;
@@ -365,7 +367,8 @@ export default {
this.$yrouter.replace({
path: "/pages/order/MyOrder/index",
query: {
type: 1
type: 1,
tabIndex: 1
}
})
})
@@ -376,7 +379,8 @@ export default {
this.$yrouter.replace({
path: "/pages/order/MyOrder/index",
query: {
type: 1
type: 1,
tabIndex: 1
}
});
})
@@ -0,0 +1,164 @@
<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 class="tips tips2">*图片由云灵AI生成仅供参考</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
},
show(val) {
if (val) {
this.currentIndex = this.index
}
}
},
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;
}
.tips2{
color: #C52733;
}
.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>
+30 -13
View File
@@ -1,25 +1,42 @@
<template>
<view class="dialog-conent" v-show="isLogin">
<view class="gift-card-modal" :class="{'ios-cover': isIOS}">
<image class="bg-image" :src="!card.isBlind ? card.cover : card.blindBoxImage" mode="widthFix" lazy-load="false"></image>
<view v-if="card.isBlind !== 1" class="image-wrap" :style="{
top: card.productImageY*2 + 'rpx',
left: card.productImageX*2+ 'rpx'
}">
<image v-if="!isTravelGroup" class="pro-image" :src="card.giftProducts[0].productImage" mode="aspectFit|aspectFill|widthFix" lazy-load="false">
</image>
<image v-if="isTravelGroup" class="pro-image" :src="card.travelGroupProduct.mainImage" mode="aspectFit|aspectFill|widthFix" lazy-load="false">
</image>
<image
class="bg-image"
:src="!card.isBlind ? card.cover : card.blindBoxImage"
mode="widthFix"
lazy-load="false"
/>
<view
v-if="card.isBlind !== 1 && !card.isUseAiCoverImg"
:style="{
top: card.productImageY*2 + 'rpx',
left: card.productImageX*2+ 'rpx'
}"
class="image-wrap"
>
<image
v-if="!isTravelGroup"
:src="card.giftProducts[0].productImage"
mode="aspectFit|aspectFill|widthFix"
lazy-load="false"
class="pro-image"
/>
<image
v-if="isTravelGroup"
:src="card.travelGroupProduct.mainImage"
mode="aspectFit|aspectFill|widthFix"
lazy-load="false"
class="pro-image"
/>
</view>
<view
class="good-story"
v-if="card.isBlind !== 1 && card.giftProducts && card.giftProducts[0].productStory && card.isShowProductStory"
v-if="card.isBlind !== 1 && card.giftProducts && card.giftProducts[0].productStory && card.isShowProductStory && !card.isUseAiCoverImg"
:style="{
right: (card.productStoryX*2 || 0) + 'rpx',
top: (card.productStoryY*2 || 0) + 'rpx'
}"
class="good-story"
>
<view v-for="(line, j) in card.giftProducts[0].productStory.split('/n')" :key="j">{{ line }}</view>
</view>
+440 -20
View File
@@ -1,9 +1,87 @@
<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
v-if="isLoad && ((goodsInfo.productInfo && goodsInfo.productInfo.storeName) || isGroupBuy)"
class="gift-page"
>
<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"
:maxlength="300"
placeholder="请描述你想生成的图片"
class="prompt-textarea"
/>
<p class="prompt-tips">{{ aiCoverDesc.length }}/300</p>
</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"
@click="handleSelectAiCover(item)"
/>
</view>
</view>
</view>
<view class="ai-cover-bottom">
<view v-if="showGenerateBtn" class="btn-wrap">
<view class="btn" @click="handleGenerateAiCover">
{{ aiCoverGenerateLoading ? '礼包封面生成中...' : 'AI生成礼包封面' }}
<image v-if="!aiCoverGenerateLoading" :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 +208,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 +226,12 @@
:index="defaultIndex"
:message="message"
/>
<AiCoverPicker
:show.sync="showAiCoverPicker"
:list="aiCoverImgsList"
:index="aiDefaultIndex"
@confirm="onAiCoverConfirm"
/>
<passkeyborad
ref='payPwdPop'
showMoney
@@ -216,18 +300,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
@@ -235,9 +326,13 @@ export default {
data() {
return {
webUrl: this.$VUE_APP_RESOURCES_URL,
isLoad: false,
loading: true,
// 盲盒礼包
isBlind: false,
// 一人领取所有礼品
isOneRecipient: false,
// 是否支持好友转赠
canTransfer: false,
message: '',
cartId: '',
@@ -258,6 +353,7 @@ export default {
isShowPayPwdPop: false,
payPassword: '',
payType: '',
// 是否支付成功
isPayOk: false,
giftKey: '',
payPrice: 0,
@@ -271,13 +367,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 +442,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 +472,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 +646,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 +703,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 +756,24 @@ export default {
this.$forceUpdate()
})
},
payNowToSendGift() {
if (this.selectCoverType === 'default') {
this.showPayPicker = true
return
}
if (this.selectCoverType === 'ai') {
if (!this.selectedAiCoverImage.id) {
uni.showToast({
title: '请生成礼包封面再操作',
icon: 'none'
})
return
} else {
this.showPayPicker = true
}
return
}
},
pwdPopFinish(payPwd) {
this.payPassword = payPwd;
this.confirmGift(true);
@@ -531,8 +785,27 @@ 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)
}
},
handleSelectAiCover(cover) {
this.selectedAiCoverImage = cover
this.aiDefaultIndex = this.aiCoverImgsList.findIndex(item => item.id === cover.id)
this.selectedCover = cover.cover
this.giftImage = cover.image
this.templateId = cover.id
},
showTips() {
this.$refs.giftTips.showTips = true
@@ -563,6 +836,7 @@ export default {
// this.couponId = res.data.couponList.usable[0] && res.data.couponList.usable[0].id || ''
// }
this.computedPrice(this.goodsInfo.cartNum)
this.isLoad = true
}).finally(() => {
this.loading = false
})
@@ -570,9 +844,13 @@ export default {
isDrawEligible(flag) {
return flag === true || flag === 1 || flag === '1' || flag === 'true'
},
handleGiftPaySuccess(isEligibleForDraw = false) {
handleGiftPaySuccess(data) {
if (data.giftCard) {
this.selectedCover = data.giftCard.cover || this.selectedCover
this.giftImage = data.giftCard.giftCardImage || this.giftImage
}
this.isPayOk = true
this.isEligibleForDraw = this.isDrawEligible(isEligibleForDraw)
this.isEligibleForDraw = this.isDrawEligible(data.isEligibleForDraw)
if (this.isCountyLotteryGift && this.isEligibleForDraw) {
this.showLotteryGiftPopup = true
}
@@ -616,7 +894,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 +908,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,
@@ -676,7 +957,7 @@ export default {
icon: "none",
duration: 2000
});
this.handleGiftPaySuccess(data.isEligibleForDraw);
this.handleGiftPaySuccess(data);
break;
case "WECHAT_H5_PAY":
// H5支付
@@ -693,7 +974,7 @@ export default {
icon: "none",
duration: 2000
});
this.handleGiftPaySuccess(data.isEligibleForDraw);
this.handleGiftPaySuccess(data);
}).catch(err => {
// this.makeAgain()
@@ -714,7 +995,7 @@ export default {
});
break;
}
}) .catch(err => {
}).catch(err => {
console.log(err);
uni.hideLoading();
uni.showToast({
@@ -722,6 +1003,14 @@ export default {
icon: "none",
duration: 2000
});
// Fix bug#6073-订单超时后需自动刷新该页面
if (err.res.statusCode === 5113) {
if(this.isGroupBuy) {
this.getGroupBuyInfo()
} else {
this.getGift()
}
}
}).finally(() => {
this.isShowPayPwdPop = false;
this.$refs.payPwdPop.clear();//清空支付密码
@@ -732,6 +1021,7 @@ export default {
const params = {
travelGroupProductId: this.cartId
}
this.isLoad = false
createGroupBuyGift(params).then(res => {
if (res.status === 200) {
this.coverList = res.data.giftCardTemplates || []
@@ -747,6 +1037,7 @@ export default {
}
this.giftNotice = res.data.giftCardGiftNotice
this.computedTravelGroupPrice(true)
this.isLoad = true
}
})
}
@@ -814,11 +1105,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 +1131,6 @@ export default {
}
.top-card{
// height: 280rpx;
background: #fff;
border-radius: 16rpx;
}
.line{
@@ -897,7 +1183,6 @@ export default {
}
.popCover{
width: 100%;
border-radius: 16rpx;
}
.lottery-gift-popup{
background: #fff;
@@ -973,4 +1258,139 @@ 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: 40rpx 0 40rpx 40rpx;
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 12rpx 20rpx;
box-sizing: border-box;
overflow-y: auto;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
}
.prompt-tips {
font-size: 24rpx;
color: #999;
text-align: right;
}
}
.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: 58rpx;
height: 58rpx;
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>
+2 -1
View File
@@ -5,7 +5,8 @@
:card="giftCard"
:isTravelGroup="isTravelGroup"
:addr="giftChooseAddress"
@receive="onReceive($event)" />
@receive="onReceive($event)"
/>
<view v-else>
<view class="no-img-wrap" v-if="isExpire">
<view class="wrap-box" >
+51 -6
View File
@@ -17,13 +17,16 @@
</view>
<view class="amount-box">
<view class="title bold">提现金额</view>
<view class="v12-justify-between">
<view class="title bold">提现金额</view>
<view class="title" @click="showRules">提现规则</view>
</view>
<view class="item acea-row row-middle">
<view class="dollar bold"></view>
<view class="input">
<u--input
v-model="amout"
:placeholder="'最低提现金额' + minPrice"
placeholder="请输入提现金额"
:maxlength="-1"
:customStyle="{
'border': 0
@@ -68,18 +71,25 @@
</block>
</blPaymentPasswordInput>
</uni-popup>
<uni-popup ref="rulesPopup" class="rules-popup" safe-area background-color="#fff" type="bottom">
<view class="rules-content">
<view class="rules-title">提现规则</view>
<rich-text v-if="withdrawalRules" class="rules-text" :nodes="withdrawalRules" />
<view v-else class="rules-text">暂无提现规则</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {mapGetters} from "vuex";
import { getBank, bankCardList, postCashInfo } from "@/api/user";
import { getBank, bankCardList, postCashInfo, getWithdrawalRules } from "@/api/user";
import NP from "number-precision";
import uniPopup from '@/components/uni-popup/uni-popup.vue';
import blPaymentPasswordInput from '@/components/blPaymentPasswordInput.vue';
import { pageListenMixins } from '@/mixins/pageListenMixins'
import { formatRichText } from '@/utils'
export default {
mixins: [pageListenMixins],
@@ -97,7 +107,8 @@ export default {
gdLimit: 0, //最低固定费率额度
feeRate: 0, //手续费费率
remark: "", //提现说明
pageKeyId: Object.freeze('userExtractIndex')
pageKeyId: Object.freeze('userExtractIndex'),
withdrawalRules: '',
}
},
components: {
@@ -135,6 +146,12 @@ export default {
}
},
methods: {
showRules() {
getWithdrawalRules().then(res => {
this.withdrawalRules = formatRichText(res.data);
this.$refs.rulesPopup.open();
})
},
calculateSxf: function (amout) {
var sxf = 0;
if (amout == 0) {
@@ -477,4 +494,32 @@ export default {
line-height: 40rpx;
text-decoration: underline;
}
</style>
.rules-popup /deep/ .uni-popup__wrapper-box {
background: #FFFFFF;
border-radius: 24rpx 24rpx 0 0;
}
.rules-content {
padding: 32rpx;
padding-bottom: calc(32rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.rules-title {
margin-bottom: 24rpx;
color: #333333;
font-size: 32rpx;
font-weight: bold;
line-height: 44rpx;
text-align: center;
}
.rules-text {
color: #666666;
font-size: 26rpx;
line-height: 40rpx;
word-break: break-all;
}
</style>
+12
View File
@@ -34,6 +34,7 @@ export default {
pages: 0, // 总页数
list: [],
os: '',
isClick: false, // 防止连续点击重复请求/跳转
pageKeyId: Object.freeze('investmentList')
}
},
@@ -41,6 +42,10 @@ export default {
this.getOS()
this.getData()
},
onShow() {
// 从文件阅读页返回时解锁,允许再次点击
this.isClick = false
},
onReachBottom() {
if (this.pages >= this.page) this.getData()
},
@@ -67,6 +72,8 @@ export default {
})
},
getDetail(id) {
if (this.isClick) return
this.isClick = true
investmentInfo(id).then(({data}) => {
const file = data.contents[0];
if (file.suffix === 'pdf' && this.os === 'android') {
@@ -77,6 +84,8 @@ export default {
url: '/pkg_common/views/pdf?path=' + encodeURI(path) + '&id=' + id
})
}
}).catch(() => {
this.isClick = false
})
},
viewPdf(url) {
@@ -89,8 +98,10 @@ export default {
fileType: 'pdf',
success: result => {
//成功
this.isClick = false
},
fail: err => {
this.isClick = false
uni.showModal({
title: 'openDocument fail',
content: JSON.stringify(err)
@@ -99,6 +110,7 @@ export default {
})
},
fail: err => {
this.isClick = false
uni.showModal({
title: 'downloadFile fail',
content: JSON.stringify(err)
+2 -2
View File
@@ -9,8 +9,8 @@
<view class="location-box flex ai-center">
<!-- @click="goCity"-->
<image :src="webUrl+'/20220903142929759087.png'" mode="scaleToFill"></image>
<text class="city-name">{{ cityName }}</text>
<text>当前定位</text>
<text class="city-name">当前城市{{ cityName }}</text>
<text></text>
</view>
<view class="more-box flex ai-center">
<text @click="goClass">全部商品列表</text>