feat: 新增地址提交与分享功能,优化活动记录列表显示用户及奖品信息,并支持抽奖页开发者模式
This commit is contained in:
parent
a3ec9c102d
commit
3dde150cde
@ -102,6 +102,14 @@ export function cancelShipping(user_id, batch_no) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/inventory/cancel-shipping`, method: 'POST', data: { batch_no } })
|
||||
}
|
||||
|
||||
export function createAddressShare(user_id, inventory_id) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/inventory/address-share/create`, method: 'POST', data: { inventory_id } })
|
||||
}
|
||||
|
||||
export function revokeAddressShare(user_id, inventory_id) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/inventory/address-share/revoke`, method: 'POST', data: { inventory_id } })
|
||||
}
|
||||
|
||||
export function getItemCards(user_id, status, page = 1, page_size = 20) {
|
||||
const data = { page, page_size }
|
||||
if (status !== undefined) data.status = status
|
||||
|
||||
@ -1,15 +1,29 @@
|
||||
<template>
|
||||
<view class="records-wrapper">
|
||||
<view class="records-list" v-if="records && records.length">
|
||||
<view v-for="(item, idx) in records" :key="item.id || idx" class="record-item">
|
||||
<image class="record-img" :src="item.image" mode="aspectFill" />
|
||||
<view class="record-info">
|
||||
<view class="record-title">{{ item.title }}</view>
|
||||
<view class="record-meta">
|
||||
<text class="record-count">x{{ item.count }}</text>
|
||||
<!-- <text v-if="item.percent" class="record-percent">{{ item.percent }}%</text> -->
|
||||
<view v-for="(item, idx) in records" :key="item.id ? `${item.id}_${idx}` : idx" class="record-item">
|
||||
<!-- 用户信息 (左侧, 紧凑) -->
|
||||
<view class="user-info-section">
|
||||
<image class="user-avatar" :src="item.avatar || defaultAvatar" mode="aspectFill" />
|
||||
<view class="user-detail">
|
||||
<text class="user-name">{{ item.user_name }}</text>
|
||||
<text class="record-time">{{ formatTime(item.created_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 奖品信息 (右侧, 扩展) -->
|
||||
<view class="prize-info-section">
|
||||
<view class="prize-image-wrap">
|
||||
<image class="record-img" :src="item.image" mode="aspectFill" />
|
||||
<view class="level-badge" v-if="item.level_name">{{ item.level_name }}</view>
|
||||
</view>
|
||||
<view class="record-info">
|
||||
<view class="record-title">{{ item.title }}</view>
|
||||
<view class="record-meta">
|
||||
<text class="record-count">x1</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty-state-compact" v-else>
|
||||
@ -23,6 +37,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
defineProps({
|
||||
records: {
|
||||
type: Array,
|
||||
@ -33,6 +49,21 @@ defineProps({
|
||||
default: '暂无购买记录'
|
||||
}
|
||||
})
|
||||
|
||||
const defaultAvatar = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
|
||||
|
||||
function formatTime(t) {
|
||||
if (!t) return ''
|
||||
const d = new Date(t)
|
||||
if (isNaN(d.getTime())) return t // 如果解析失败直接返回
|
||||
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
const ss = String(d.getSeconds()).padStart(2, '0')
|
||||
return `${m}-${day} ${hh}:${mm}:${ss}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -42,8 +73,9 @@ defineProps({
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: $spacing-sm 0;
|
||||
padding: $spacing-md $spacing-sm;
|
||||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.03);
|
||||
|
||||
&:last-child {
|
||||
@ -51,43 +83,121 @@ defineProps({
|
||||
}
|
||||
}
|
||||
|
||||
.record-img {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: $radius-md;
|
||||
margin-right: $spacing-md;
|
||||
.record-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: $spacing-md $spacing-sm;
|
||||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.03);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-xs;
|
||||
flex: 0 0 35%; // 固定宽度给用户信息
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 50%;
|
||||
background: $bg-secondary;
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 24rpx;
|
||||
color: $text-main;
|
||||
font-weight: 500;
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 20rpx;
|
||||
color: $text-sub;
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
|
||||
.prize-info-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.prize-image-wrap {
|
||||
position: relative;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.record-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: $radius-md;
|
||||
background: $bg-secondary;
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.level-badge {
|
||||
position: absolute;
|
||||
top: -6rpx;
|
||||
right: -6rpx;
|
||||
background: $gradient-gold;
|
||||
color: #fff;
|
||||
font-size: 16rpx;
|
||||
padding: 2rpx 6rpx;
|
||||
border-radius: 4rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2rpx 4rpx rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.record-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start; // 左对齐
|
||||
}
|
||||
|
||||
.record-title {
|
||||
font-size: $font-md;
|
||||
font-weight: 600;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: $text-main;
|
||||
margin-bottom: $spacing-xs;
|
||||
@include text-ellipsis(1);
|
||||
@include text-ellipsis(2); // 允许两行
|
||||
line-height: 1.3;
|
||||
margin-bottom: 4rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.record-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
}
|
||||
|
||||
.record-count {
|
||||
font-size: $font-sm;
|
||||
font-size: 20rpx;
|
||||
color: $brand-primary;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.record-percent {
|
||||
font-size: $font-xs;
|
||||
color: $text-sub;
|
||||
background: rgba($brand-primary, 0.08);
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
/* 紧凑优雅的空状态 */
|
||||
|
||||
@ -24,25 +24,26 @@ export function useRecords() {
|
||||
const res = await getIssueDrawLogs(activityId, issueId)
|
||||
const list = (res && res.list) || (Array.isArray(res) ? res : [])
|
||||
|
||||
// 聚合同一奖品的记录
|
||||
const aggregate = {}
|
||||
list.forEach(it => {
|
||||
const key = it.reward_id || it.id
|
||||
if (!aggregate[key]) {
|
||||
aggregate[key] = {
|
||||
id: key,
|
||||
title: it.reward_name || it.title || it.name || '-',
|
||||
image: it.reward_image || it.image || '',
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
aggregate[key].count += 1
|
||||
})
|
||||
// 直接使用原始记录列表,不进行聚合
|
||||
// 映射字段以符合 RecordsList 组件的展示需求
|
||||
winRecords.value = list.map(it => ({
|
||||
id: it.id,
|
||||
title: it.reward_name || it.title || it.name || '-', // 奖品名称
|
||||
image: it.reward_image || it.image || '', // 奖品图片
|
||||
count: 1, // 单个记录数量为1
|
||||
|
||||
// const total = list.length || 1
|
||||
winRecords.value = Object.values(aggregate).map(it => ({
|
||||
...it,
|
||||
// percent: ((it.count / total) * 100).toFixed(1)
|
||||
// 用户信息
|
||||
user_id: it.user_id,
|
||||
user_name: it.user_name || '匿名用户',
|
||||
avatar: it.avatar,
|
||||
|
||||
// 时间信息
|
||||
created_at: it.created_at,
|
||||
|
||||
// 其他元数据
|
||||
is_winner: it.is_winner,
|
||||
level: it.level,
|
||||
level_name: getLevelName(it.level)
|
||||
}))
|
||||
} catch (e) {
|
||||
console.error('fetchWinRecords error', e)
|
||||
@ -52,6 +53,13 @@ export function useRecords() {
|
||||
}
|
||||
}
|
||||
|
||||
function getLevelName(level) {
|
||||
if (!level) return ''
|
||||
// 尝试映射 1->A, 2->B ...
|
||||
const map = { 1: 'A', 2: 'B', 3: 'C', 4: 'D', 5: 'E', 6: 'F', 7: 'G', 8: 'H', 9: 'I', 10: 'J' }
|
||||
return map[level] || (level + '赏')
|
||||
}
|
||||
|
||||
function clearRecords() {
|
||||
winRecords.value = []
|
||||
}
|
||||
|
||||
@ -90,6 +90,12 @@
|
||||
"navigationBarTitleText": "编辑地址"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/address/submit",
|
||||
"style": {
|
||||
"navigationBarTitleText": "填写收货信息"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/help/index",
|
||||
"style": {
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
<template #footer>
|
||||
<!-- 底部多档位抽赏按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<view class="bottom-actions" v-if="!isDevMode">
|
||||
<view class="tier-btn" @tap="openPayment(1)">
|
||||
<text class="tier-price">¥{{ (pricePerDraw * 1).toFixed(2) }}</text>
|
||||
<text class="tier-label">抽1发</text>
|
||||
@ -48,6 +48,30 @@
|
||||
<text class="tier-label">抽10发</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 开发者模式 -->
|
||||
<view class="bottom-actions dev-actions" v-else>
|
||||
<view class="dev-input-wrapper">
|
||||
<text class="dev-label">自定义发数:</text>
|
||||
<input
|
||||
class="dev-input"
|
||||
type="number"
|
||||
v-model="customDrawCount"
|
||||
placeholder="输入数量"
|
||||
/>
|
||||
</view>
|
||||
<view class="tier-btn tier-hot" @tap="openPayment(customDrawCount)">
|
||||
<text class="tier-price">Go</text>
|
||||
<text class="tier-label">抽{{ customDrawCount }}发</text>
|
||||
</view>
|
||||
<view class="tier-btn" @tap="isDevMode = false">
|
||||
<text class="tier-price">Exit</text>
|
||||
<text class="tier-label">退出</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Dev Toggle Button -->
|
||||
<view class="dev-fab" @tap="isDevMode = !isDevMode">Dev</view>
|
||||
</template>
|
||||
|
||||
<template #modals>
|
||||
@ -141,6 +165,9 @@ const showResultPopup = ref(false)
|
||||
const drawResults = ref([])
|
||||
const drawLoading = ref(false)
|
||||
|
||||
const isDevMode = ref(false)
|
||||
const customDrawCount = ref(1)
|
||||
|
||||
// 支付相关
|
||||
const paymentVisible = ref(false)
|
||||
const paymentAmount = ref('0.00')
|
||||
@ -517,4 +544,61 @@ watch(currentIssueId, (newId) => {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dev-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.dev-input-wrapper {
|
||||
flex: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
background: #f5f5f5;
|
||||
border-radius: 20rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dev-label {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.dev-input {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
color: #333;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.dev-fab {
|
||||
position: fixed;
|
||||
right: 32rpx;
|
||||
bottom: 280rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(10rpx);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
z-index: 990;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.15);
|
||||
border: 1rpx solid rgba(255,255,255,0.2);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.9);
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
210
pages/address/submit.vue
Normal file
210
pages/address/submit.vue
Normal file
@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header glass-card">
|
||||
<view class="title">填写收货信息</view>
|
||||
<view class="desc">好友正在为您申请奖品发货,请填写您的准确收货地址</view>
|
||||
</view>
|
||||
|
||||
<view class="form glass-card">
|
||||
<view class="form-item">
|
||||
<text class="label">收货人</text>
|
||||
<input v-model="form.name" placeholder="请输入姓名" class="input" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">手机号码</text>
|
||||
<input v-model="form.mobile" type="number" maxlength="11" placeholder="请输入手机号" class="input" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">地区</text>
|
||||
<picker mode="region" @change="onRegionChange" class="input">
|
||||
<view class="picker-value" v-if="form.province">
|
||||
{{ form.province }} {{ form.city }} {{ form.district }}
|
||||
</view>
|
||||
<view class="picker-placeholder" v-else>请选择省市区</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">详细地址</text>
|
||||
<textarea v-model="form.address" placeholder="街道、楼牌号等" class="textarea" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-btn">
|
||||
<button class="submit-btn" :loading="loading" @tap="onSubmit">确认提交</button>
|
||||
</view>
|
||||
|
||||
<view class="tip-section">
|
||||
<text class="tip-text">* 请确保信息准确,提交后无法修改</text>
|
||||
<text class="tip-text" v-if="isLoggedIn">* 您已登录,提交后该奖品将转移至您的账户下</text>
|
||||
<text class="tip-text" v-else>* 您当前未登录,提交后资产仍归属于原发起人</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
const token = ref('')
|
||||
const loading = ref(false)
|
||||
const isLoggedIn = ref(!!uni.getStorageSync('token'))
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
mobile: '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
address: ''
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.token) {
|
||||
token.value = options.token
|
||||
} else {
|
||||
uni.showToast({ title: '参数错误', icon: 'none' })
|
||||
}
|
||||
})
|
||||
|
||||
function onRegionChange(e) {
|
||||
const [p, c, d] = e.detail.value
|
||||
form.province = p
|
||||
form.city = c
|
||||
form.district = d
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
if (!token.value) return
|
||||
if (!form.name || !form.mobile || !form.province || !form.address) {
|
||||
uni.showToast({ title: '请完善收货信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!/^1\d{10}$/.test(form.mobile)) {
|
||||
uni.showToast({ title: '手机号格式错误', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/api/app/address-share/submit',
|
||||
method: 'POST',
|
||||
data: {
|
||||
share_token: token.value,
|
||||
...form
|
||||
},
|
||||
// 手动带上 token,因为公共 request 可能不带
|
||||
header: {
|
||||
'Authorization': uni.getStorageSync('token') || ''
|
||||
}
|
||||
})
|
||||
|
||||
uni.showModal({
|
||||
title: '提交成功',
|
||||
content: '收货信息已提交,请等待发货。' + (isLoggedIn.value ? '资产已转移至您的盒柜。' : ''),
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e.message || '提交失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
padding: 30rpx;
|
||||
min-height: 100vh;
|
||||
background: $bg-page;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 40rpx;
|
||||
margin-bottom: 30rpx;
|
||||
animation: fadeInDown 0.5s ease-out;
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: $text-main;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 26rpx;
|
||||
color: $text-sub;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
padding: 20rpx 40rpx;
|
||||
animation: fadeInUp 0.5s ease-out 0.1s backwards;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
padding: 30rpx 0;
|
||||
border-bottom: 1rpx solid rgba(0,0,0,0.05);
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: $text-main;
|
||||
margin-bottom: 20rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.input, .textarea {
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
color: $text-main;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
height: 160rpx;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.picker-placeholder { color: $text-tertiary; }
|
||||
}
|
||||
|
||||
.footer-btn {
|
||||
margin-top: 60rpx;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
height: 88rpx;
|
||||
background: $gradient-brand;
|
||||
color: #fff;
|
||||
border-radius: $radius-round;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: $shadow-warm;
|
||||
|
||||
&:active { transform: scale(0.98); opacity: 0.9; }
|
||||
}
|
||||
|
||||
.tip-section {
|
||||
margin-top: 40rpx;
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 22rpx;
|
||||
color: $text-tertiary;
|
||||
}
|
||||
</style>
|
||||
@ -37,6 +37,7 @@
|
||||
<text class="item-name">{{ item.name || '未命名道具' }}</text>
|
||||
<text class="item-price" v-if="item.price">单价: ¥{{ item.price }}</text>
|
||||
<view class="item-actions">
|
||||
<text class="invite-btn" v-if="!item.selected" @tap.stop="onInvite(item)">邀请填写</text>
|
||||
<text class="item-count" v-if="!item.selected">x{{ item.count || 1 }}</text>
|
||||
<view class="stepper" v-else @tap.stop>
|
||||
<text class="step-btn minus" @tap.stop="changeCount(item, -1)">-</text>
|
||||
@ -133,17 +134,44 @@
|
||||
<view v-if="loading && shippedList.length > 0" class="loading-more">加载更多...</view>
|
||||
<view v-if="!hasMore && shippedList.length > 0" class="no-more">没有更多了</view>
|
||||
</block>
|
||||
<!-- 分享弹窗 -->
|
||||
<view class="share-mask" v-if="showSharePopup" @tap="showSharePopup = false" @touchmove.stop></view>
|
||||
<view class="share-popup glass-card" :class="{ 'show': showSharePopup }">
|
||||
<view class="share-header">
|
||||
<text class="share-title">邀请好友填写地址</text>
|
||||
<text class="share-close" @tap="showSharePopup = false">×</text>
|
||||
</view>
|
||||
<view class="share-body">
|
||||
<view class="share-item-preview">
|
||||
<image class="preview-img" :src="sharingItem.image" mode="aspectFit"></image>
|
||||
<view class="preview-info">
|
||||
<text class="preview-name">{{ sharingItem.name }}</text>
|
||||
<text class="preview-desc">邀请好友填写地址后,该奖品将发货至好友手中,并认领归属于分享账号。</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="share-actions">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="action-btn share-card-btn" open-type="share">发送给微信好友</button>
|
||||
<!-- #endif -->
|
||||
<button class="action-btn copy-link-btn" @tap="onCopyShareLink">复制分享链接</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onShow, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getInventory, getProductDetail, redeemInventory, requestShipping, cancelShipping, listAddresses, getShipments } from '@/api/appUser'
|
||||
import { onShow, onReachBottom, onShareAppMessage } from '@dcloudio/uni-app'
|
||||
import { getInventory, getProductDetail, redeemInventory, requestShipping, cancelShipping, listAddresses, getShipments, createAddressShare } from '@/api/appUser'
|
||||
|
||||
const currentTab = ref(0)
|
||||
const aggregatedList = ref([])
|
||||
const shippedList = ref([])
|
||||
const showSharePopup = ref(false)
|
||||
const sharingItem = ref({})
|
||||
const currentShareToken = ref('')
|
||||
const currentShortLink = ref('')
|
||||
const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(100)
|
||||
@ -750,11 +778,73 @@ function onCancelShipping(shipment) {
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e?.message || '取消失败', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// 微信分享逻辑
|
||||
onShareAppMessage((res) => {
|
||||
showSharePopup.value = false
|
||||
return {
|
||||
title: `送你一个好礼,快来填写地址领走吧!`,
|
||||
path: `/pages/address/submit?token=${currentShareToken.value}`,
|
||||
imageUrl: sharingItem.value.image || '/static/logo.png'
|
||||
}
|
||||
})
|
||||
|
||||
async function onInvite(item) {
|
||||
uni.vibrateShort({ type: 'medium' })
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
if (!user_id) {
|
||||
uni.navigateTo({ url: '/pages/login/index' })
|
||||
return
|
||||
}
|
||||
|
||||
// 获取第一个可用的 inventory id
|
||||
const invId = item.original_ids && item.original_ids[0]
|
||||
if (!invId) {
|
||||
uni.showToast({ title: '无效的资产', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '准备分享...' })
|
||||
try {
|
||||
const res = await createAddressShare(user_id, invId)
|
||||
// 兼容不同的数据返回格式
|
||||
currentShareToken.value = res.data?.share_token || res.share_token
|
||||
currentShortLink.value = res.data?.short_link || res.short_link || ''
|
||||
|
||||
// 准备分享预览数据
|
||||
sharingItem.value = {
|
||||
id: invId,
|
||||
name: item.name,
|
||||
image: item.image,
|
||||
count: item.count
|
||||
}
|
||||
|
||||
// 显示分享弹窗
|
||||
showSharePopup.value = true
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e.message || '生成失败', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
function onCopyShareLink() {
|
||||
let url = currentShortLink.value
|
||||
if (!url) {
|
||||
url = `${window?.location?.origin || ''}/pages/address/submit?token=${currentShareToken.value}`
|
||||
}
|
||||
|
||||
uni.setClipboardData({
|
||||
data: url,
|
||||
success: () => {
|
||||
uni.showToast({ title: '已复制链接', icon: 'success' })
|
||||
showSharePopup.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -967,9 +1057,22 @@ function onCancelShipping(shipment) {
|
||||
.item-actions {
|
||||
margin-top: auto;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.invite-btn {
|
||||
font-size: 22rpx;
|
||||
color: $brand-primary;
|
||||
background: rgba($brand-primary, 0.1);
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
background: rgba($brand-primary, 0.2);
|
||||
}
|
||||
}
|
||||
.item-count {
|
||||
font-size: 28rpx;
|
||||
color: $text-main;
|
||||
@ -1248,4 +1351,120 @@ function onCancelShipping(shipment) {
|
||||
.bottom-spacer {
|
||||
height: 120rpx;
|
||||
}
|
||||
|
||||
.share-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 998;
|
||||
backdrop-filter: blur(4rpx);
|
||||
}
|
||||
|
||||
.share-popup {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
border-radius: 40rpx 40rpx 0 0;
|
||||
z-index: 999;
|
||||
padding: 40rpx;
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
|
||||
&.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.share-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.share-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: $text-main;
|
||||
}
|
||||
|
||||
.share-close {
|
||||
font-size: 40rpx;
|
||||
color: $text-sub;
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.share-item-preview {
|
||||
display: flex;
|
||||
background: #f8f9fa;
|
||||
padding: 30rpx;
|
||||
border-radius: 24rpx;
|
||||
margin-bottom: 50rpx;
|
||||
|
||||
.preview-img {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-right: 20rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.preview-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.preview-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: $text-main;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.preview-desc {
|
||||
font-size: 20rpx;
|
||||
color: $text-sub;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.share-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
|
||||
.action-btn {
|
||||
height: 90rpx;
|
||||
border-radius: 45rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
|
||||
&::after { border: none; }
|
||||
|
||||
&.share-card-btn {
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.copy-link-btn {
|
||||
background: #f0f0f0;
|
||||
color: $text-main;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user