feat: 移除注册页,新增邀请落地页,优化分享流程、积分展示及活动加载,并添加分享图片。
This commit is contained in:
parent
3dde150cde
commit
e19ec06d74
@ -4,34 +4,29 @@
|
|||||||
<view class="cabinet-panel" @tap.stop>
|
<view class="cabinet-panel" @tap.stop>
|
||||||
<view class="cabinet-header">
|
<view class="cabinet-header">
|
||||||
<text class="cabinet-title">我的盒柜</text>
|
<text class="cabinet-title">我的盒柜</text>
|
||||||
<text class="cabinet-close" @tap="close">×</text>
|
<view class="cabinet-actions">
|
||||||
|
<text class="view-all" @tap="goFullCabinet">查看全部</text>
|
||||||
|
<text class="cabinet-close" @tap="close">×</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<scroll-view scroll-y class="cabinet-content">
|
<view v-if="loading" class="cabinet-loading">
|
||||||
<view v-if="loading" class="cabinet-loading">
|
<text class="loading-text">加载中...</text>
|
||||||
<text class="loading-icon">📦</text>
|
</view>
|
||||||
<text class="loading-text">加载中...</text>
|
|
||||||
</view>
|
<view v-else-if="items.length === 0" class="cabinet-empty">
|
||||||
|
<text class="empty-text">暂无物品,参与活动获取奖品</text>
|
||||||
<view v-else-if="items.length === 0" class="cabinet-empty">
|
</view>
|
||||||
<text class="empty-icon">🎁</text>
|
|
||||||
<text class="empty-text">暂无物品</text>
|
<scroll-view v-else scroll-x class="cabinet-scroll">
|
||||||
<text class="empty-hint">参与活动获得奖品后会显示在这里</text>
|
<view class="thumb-list">
|
||||||
</view>
|
<view v-for="item in displayItems" :key="item.id" class="thumb-item">
|
||||||
|
<image class="thumb-img" :src="item.image" mode="aspectFill" />
|
||||||
<view v-else class="cabinet-grid">
|
<text class="thumb-count">x{{ item.count }}</text>
|
||||||
<view v-for="item in displayItems" :key="item.id" class="cabinet-item">
|
</view>
|
||||||
<image class="item-image" :src="item.image" mode="aspectFill" />
|
<view v-if="hasMore" class="thumb-more" @tap="goFullCabinet">
|
||||||
<view class="item-info">
|
<text>+{{ items.length - maxDisplay }}</text>
|
||||||
<text class="item-name">{{ item.name }}</text>
|
|
||||||
<text class="item-count">x{{ item.count }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="hasMore" class="load-more" @tap="goFullCabinet">
|
|
||||||
<text>查看全部 {{ total }} 件物品</text>
|
|
||||||
<text class="arrow">›</text>
|
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@ -40,16 +35,11 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { getInventory } from '@/api/appUser'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: { type: Boolean, default: false },
|
||||||
type: Boolean,
|
activityId: { type: [String, Number], default: '' }
|
||||||
default: false
|
|
||||||
},
|
|
||||||
activityId: {
|
|
||||||
type: [String, Number],
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:visible'])
|
const emit = defineEmits(['update:visible'])
|
||||||
@ -57,203 +47,189 @@ const emit = defineEmits(['update:visible'])
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const maxDisplay = 6 // 最多显示6个
|
const maxDisplay = 8
|
||||||
|
|
||||||
const displayItems = computed(() => items.value.slice(0, maxDisplay))
|
const displayItems = computed(() => items.value.slice(0, maxDisplay))
|
||||||
const hasMore = computed(() => items.value.length > maxDisplay || total.value > items.value.length)
|
const hasMore = computed(() => items.value.length > maxDisplay)
|
||||||
|
|
||||||
function close() {
|
function close() { emit('update:visible', false) }
|
||||||
emit('update:visible', false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function goFullCabinet() {
|
function goFullCabinet() {
|
||||||
close()
|
close()
|
||||||
uni.switchTab({ url: '/pages/cabinet/index' })
|
uni.switchTab({ url: '/pages/cabinet/index' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟加载数据,实际项目中应该调用API
|
function cleanUrl(u) {
|
||||||
|
if (!u) return '/static/logo.png'
|
||||||
|
let s = String(u).trim()
|
||||||
|
if (s.startsWith('[') && s.endsWith(']')) {
|
||||||
|
try { const arr = JSON.parse(s); if (Array.isArray(arr) && arr.length > 0) s = arr[0] } catch (e) {}
|
||||||
|
}
|
||||||
|
s = s.replace(/[`'"]/g, '').trim()
|
||||||
|
const m = s.match(/https?:\/\/[^\s]+/)
|
||||||
|
if (m && m[0]) return m[0]
|
||||||
|
return s || '/static/logo.png'
|
||||||
|
}
|
||||||
|
|
||||||
async function loadItems() {
|
async function loadItems() {
|
||||||
if (!props.activityId) return
|
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
// TODO: 调用实际API获取当前活动相关的盒柜物品
|
const userId = uni.getStorageSync('user_id')
|
||||||
// const res = await getUserCabinetItems(props.activityId)
|
if (!userId) { items.value = []; total.value = 0; return }
|
||||||
// items.value = res.items || []
|
const res = await getInventory(userId, 1, 50)
|
||||||
// total.value = res.total || 0
|
let list = []
|
||||||
|
let rawTotal = 0
|
||||||
|
if (res && Array.isArray(res.list)) { list = res.list; rawTotal = res.total || 0 }
|
||||||
|
else if (res && Array.isArray(res.data)) { list = res.data; rawTotal = res.total || 0 }
|
||||||
|
else if (Array.isArray(res)) { list = res; rawTotal = res.length }
|
||||||
|
|
||||||
// 模拟数据
|
const filtered = list.filter(item => Number(item.status) === 1 && !item.has_shipment)
|
||||||
await new Promise(r => setTimeout(r, 300))
|
const aggregated = new Map()
|
||||||
|
filtered.forEach(item => {
|
||||||
|
const key = String(item.product_id || item.id)
|
||||||
|
if (aggregated.has(key)) {
|
||||||
|
aggregated.get(key).count += 1
|
||||||
|
} else {
|
||||||
|
aggregated.set(key, {
|
||||||
|
id: key,
|
||||||
|
name: (item.product_name || item.name || '').trim() || '未知物品',
|
||||||
|
image: cleanUrl(item.product_images || item.image),
|
||||||
|
count: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
items.value = Array.from(aggregated.values())
|
||||||
|
total.value = rawTotal
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[CabinetPreviewPopup] 加载失败', e)
|
||||||
items.value = []
|
items.value = []
|
||||||
total.value = 0
|
total.value = 0
|
||||||
} catch (e) {
|
|
||||||
console.error('加载盒柜失败', e)
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.visible, (v) => {
|
watch(() => props.visible, (v) => { if (v) loadItems() })
|
||||||
if (v) loadItems()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.cabinet-overlay {
|
.cabinet-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 9000;
|
z-index: 9000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cabinet-mask {
|
.cabinet-mask {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
right: 0;
|
background: rgba(0, 0, 0, 0.5);
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
backdrop-filter: blur(10rpx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cabinet-panel {
|
.cabinet-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: $spacing-lg;
|
left: 24rpx; right: 24rpx;
|
||||||
right: $spacing-lg;
|
|
||||||
bottom: calc(env(safe-area-inset-bottom) + 24rpx);
|
bottom: calc(env(safe-area-inset-bottom) + 24rpx);
|
||||||
max-height: 65vh;
|
background: rgba(255, 255, 255, 0.95);
|
||||||
background: rgba($bg-card, 0.95);
|
border-radius: 24rpx;
|
||||||
border-radius: $radius-xl;
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||||||
box-shadow: $shadow-card;
|
|
||||||
border: 1rpx solid rgba(255, 255, 255, 0.5);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
animation: slideUp 0.25s ease-out;
|
animation: slideUp 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cabinet-header {
|
.cabinet-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: $spacing-lg;
|
padding: 20rpx 24rpx;
|
||||||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cabinet-title {
|
.cabinet-title {
|
||||||
font-size: $font-lg;
|
font-size: 28rpx;
|
||||||
font-weight: 800;
|
font-weight: 700;
|
||||||
color: $text-main;
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cabinet-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-all {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #FF6B35;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cabinet-close {
|
.cabinet-close {
|
||||||
font-size: 48rpx;
|
font-size: 40rpx;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: $text-tertiary;
|
color: #999;
|
||||||
padding: 0 10rpx;
|
padding: 0 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cabinet-content {
|
.cabinet-loading, .cabinet-empty {
|
||||||
max-height: 50vh;
|
padding: 32rpx 24rpx;
|
||||||
padding: $spacing-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cabinet-loading,
|
|
||||||
.cabinet-empty {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 60rpx $spacing-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-icon,
|
|
||||||
.empty-icon {
|
|
||||||
font-size: 64rpx;
|
|
||||||
margin-bottom: $spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-text,
|
|
||||||
.empty-text {
|
|
||||||
font-size: $font-md;
|
|
||||||
color: $text-sub;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-hint {
|
|
||||||
font-size: $font-xs;
|
|
||||||
color: $text-tertiary;
|
|
||||||
margin-top: $spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cabinet-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: $spacing-md;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cabinet-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-image {
|
|
||||||
width: 100%;
|
|
||||||
aspect-ratio: 1 / 1;
|
|
||||||
border-radius: $radius-md;
|
|
||||||
background: $bg-secondary;
|
|
||||||
margin-bottom: $spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-name {
|
|
||||||
font-size: $font-xs;
|
|
||||||
color: $text-main;
|
|
||||||
font-weight: 600;
|
|
||||||
@include text-ellipsis(1);
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-count {
|
.loading-text, .empty-text {
|
||||||
font-size: 22rpx;
|
font-size: 24rpx;
|
||||||
color: $brand-primary;
|
color: #999;
|
||||||
font-weight: 700;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.load-more {
|
.cabinet-scroll {
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-list {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-item {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-img {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-count {
|
||||||
|
position: absolute;
|
||||||
|
right: 4rpx;
|
||||||
|
bottom: 4rpx;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20rpx;
|
||||||
|
padding: 2rpx 8rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-more {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #f0f0f0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8rpx;
|
font-size: 24rpx;
|
||||||
padding: $spacing-lg 0;
|
color: #666;
|
||||||
margin-top: $spacing-md;
|
|
||||||
color: $brand-primary;
|
|
||||||
font-size: $font-sm;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-top: 1rpx solid rgba(0, 0, 0, 0.04);
|
flex-shrink: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideUp {
|
@keyframes slideUp {
|
||||||
from {
|
from { transform: translateY(20rpx); opacity: 0; }
|
||||||
transform: translateY(40rpx);
|
to { transform: translateY(0); opacity: 1; }
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -81,13 +81,17 @@ const groupedResults = computed(() => {
|
|||||||
const arr = Array.isArray(props.results) ? props.results : []
|
const arr = Array.isArray(props.results) ? props.results : []
|
||||||
|
|
||||||
arr.forEach(item => {
|
arr.forEach(item => {
|
||||||
const key = item.title || item.name || '神秘奖品'
|
// 使用reward_id作为唯一key,避免同名不同产品被错误合并
|
||||||
|
const rewardId = item.reward_id || item.rewardId || item.id
|
||||||
|
const key = rewardId != null ? `rid_${rewardId}` : (item.title || item.name || '神秘奖品')
|
||||||
|
|
||||||
if (map.has(key)) {
|
if (map.has(key)) {
|
||||||
map.get(key).quantity++
|
map.get(key).quantity++
|
||||||
} else {
|
} else {
|
||||||
map.set(key, {
|
map.set(key, {
|
||||||
title: key,
|
title: item.title || item.name || '神秘奖品',
|
||||||
image: item.image || item.img || item.pic || '',
|
image: item.image || item.img || item.pic || '',
|
||||||
|
reward_id: rewardId,
|
||||||
quantity: 1
|
quantity: 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
12
pages.json
12
pages.json
@ -54,6 +54,12 @@
|
|||||||
"navigationBarTitleText": "我的道具卡"
|
"navigationBarTitleText": "我的道具卡"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/invite/landing",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "好友邀请"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/invites/index",
|
"path": "pages/invites/index",
|
||||||
"style": {
|
"style": {
|
||||||
@ -158,12 +164,6 @@
|
|||||||
"navigationBarBackgroundColor": "#000000",
|
"navigationBarBackgroundColor": "#000000",
|
||||||
"navigationBarTextStyle": "white"
|
"navigationBarTextStyle": "white"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/register/register",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
|
|||||||
@ -207,8 +207,8 @@ function openPayment(count) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
paymentVisible.value = true
|
paymentVisible.value = true
|
||||||
fetchPropCards()
|
// 并行获取道具卡和优惠券
|
||||||
fetchCoupons()
|
Promise.all([fetchPropCards(), fetchCoupons()])
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onPaymentConfirm(data) {
|
async function onPaymentConfirm(data) {
|
||||||
@ -291,6 +291,7 @@ function mapResultsToFlipItems(resultRes, poolRewards) {
|
|||||||
const fromName = !fromId && rewardName ? poolArr.find(x => x?.title === rewardName) : null
|
const fromName = !fromId && rewardName ? poolArr.find(x => x?.title === rewardName) : null
|
||||||
const it = fromId || fromName || null
|
const it = fromId || fromName || null
|
||||||
return {
|
return {
|
||||||
|
reward_id: rewardId, // 添加reward_id用于正确分组
|
||||||
title: rewardName || it?.title || '奖励',
|
title: rewardName || it?.title || '奖励',
|
||||||
image: d.image || it?.image || d.img || d.pic || d.product_image || ''
|
image: d.image || it?.image || d.img || d.pic || d.product_image || ''
|
||||||
}
|
}
|
||||||
@ -352,7 +353,19 @@ async function onMachineDraw(count) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const resultRes = await getLotteryResult(orderNo)
|
// 轮询等待开奖完成
|
||||||
|
let resultRes = await getLotteryResult(orderNo)
|
||||||
|
let pollCount = 0
|
||||||
|
const maxPolls = 15 // 最多轮询15次,每次2秒,共30秒
|
||||||
|
|
||||||
|
while (resultRes?.status === 'paid_waiting' &&
|
||||||
|
resultRes?.completed < resultRes?.count &&
|
||||||
|
pollCount < maxPolls) {
|
||||||
|
await new Promise(r => setTimeout(r, resultRes?.nextPollMs || 2000))
|
||||||
|
resultRes = await getLotteryResult(orderNo)
|
||||||
|
pollCount++
|
||||||
|
}
|
||||||
|
|
||||||
const items = mapResultsToFlipItems(resultRes, currentIssueRewards.value)
|
const items = mapResultsToFlipItems(resultRes, currentIssueRewards.value)
|
||||||
|
|
||||||
drawResults.value = items
|
drawResults.value = items
|
||||||
@ -367,15 +380,16 @@ async function onMachineDraw(count) {
|
|||||||
// ============ 生命周期 ============
|
// ============ 生命周期 ============
|
||||||
onLoad(async (opts) => {
|
onLoad(async (opts) => {
|
||||||
const id = opts?.id || ''
|
const id = opts?.id || ''
|
||||||
if (id) {
|
if (!id) return
|
||||||
activityId.value = id
|
activityId.value = id
|
||||||
await fetchDetail()
|
// 并行获取活动详情和期数信息
|
||||||
setNavigationTitle('无限赏')
|
await Promise.all([fetchDetail(), fetchIssues()])
|
||||||
await fetchIssues()
|
setNavigationTitle('无限赏')
|
||||||
await fetchRewardsForIssues(issues.value)
|
// 期数获取完成后获取奖励
|
||||||
if (currentIssueId.value) {
|
await fetchRewardsForIssues(issues.value)
|
||||||
fetchWinRecords(id, currentIssueId.value)
|
// 异步获取记录(不阻塞渲染)
|
||||||
}
|
if (currentIssueId.value) {
|
||||||
|
fetchWinRecords(id, currentIssueId.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -298,15 +298,16 @@ function onPaymentSuccess(payload) {
|
|||||||
onLoad(async (opts) => {
|
onLoad(async (opts) => {
|
||||||
startNowTicker()
|
startNowTicker()
|
||||||
const id = opts?.id || ''
|
const id = opts?.id || ''
|
||||||
if (id) {
|
if (!id) return
|
||||||
activityId.value = id
|
activityId.value = id
|
||||||
await fetchDetail()
|
// 并行获取活动详情和期数信息
|
||||||
setNavigationTitle('一番赏')
|
await Promise.all([fetchDetail(), fetchIssues()])
|
||||||
await fetchIssues()
|
setNavigationTitle('一番赏')
|
||||||
await fetchRewardsForIssues(issues.value)
|
// 期数获取完成后获取奖励
|
||||||
if (currentIssueId.value) {
|
await fetchRewardsForIssues(issues.value)
|
||||||
fetchWinRecords(id, currentIssueId.value)
|
// 异步获取记录(不阻塞渲染)
|
||||||
}
|
if (currentIssueId.value) {
|
||||||
|
fetchWinRecords(id, currentIssueId.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
531
pages/invite/landing.vue
Normal file
531
pages/invite/landing.vue
Normal file
@ -0,0 +1,531 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page-container">
|
||||||
|
<!-- 顶部装饰背景 - 漂浮光球 -->
|
||||||
|
<view class="bg-decoration"></view>
|
||||||
|
|
||||||
|
<!-- 装饰球体 -->
|
||||||
|
<view class="orb orb-1"></view>
|
||||||
|
<view class="orb orb-2"></view>
|
||||||
|
|
||||||
|
<view class="content-wrap">
|
||||||
|
<!-- 品牌区域 -->
|
||||||
|
<view class="glass-card hero-card">
|
||||||
|
<view class="brand-section">
|
||||||
|
<view class="logo-box">
|
||||||
|
<image class="logo" src="/static/logo.png" mode="widthFix"></image>
|
||||||
|
</view>
|
||||||
|
<view class="hero-title">🎁 好友邀请</view>
|
||||||
|
<view class="welcome-text">开启欧气之旅 ✨</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 邀请人信息 -->
|
||||||
|
<view class="invite-info" v-if="inviteCode">
|
||||||
|
<view class="invite-badge">
|
||||||
|
<text class="invite-emoji">👋</text>
|
||||||
|
<view class="invite-detail">
|
||||||
|
<text class="invite-main">好友正在邀请你加入</text>
|
||||||
|
<text class="invite-code">邀请码:{{ inviteCode }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 福利卡片 -->
|
||||||
|
<view class="glass-card benefits-card">
|
||||||
|
<view class="benefits-header">
|
||||||
|
<text class="benefits-title">🎉 新人专属福利</text>
|
||||||
|
</view>
|
||||||
|
<view class="benefits-list">
|
||||||
|
<view class="benefit-item">
|
||||||
|
<view class="benefit-icon-wrap">
|
||||||
|
<text class="benefit-icon">💎</text>
|
||||||
|
</view>
|
||||||
|
<view class="benefit-text">
|
||||||
|
<text class="benefit-main">注册即送10积分</text>
|
||||||
|
<text class="benefit-sub">可用于抽奖抵扣</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="benefit-item">
|
||||||
|
<view class="benefit-icon-wrap">
|
||||||
|
<text class="benefit-icon">🎫</text>
|
||||||
|
</view>
|
||||||
|
<view class="benefit-text">
|
||||||
|
<text class="benefit-main">首单专属优惠</text>
|
||||||
|
<text class="benefit-sub">限时折扣等你拿</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="benefit-item">
|
||||||
|
<view class="benefit-icon-wrap">
|
||||||
|
<text class="benefit-icon">🃏</text>
|
||||||
|
</view>
|
||||||
|
<view class="benefit-text">
|
||||||
|
<text class="benefit-main">新手道具卡</text>
|
||||||
|
<text class="benefit-sub">免费体验玩法</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<view class="action-section">
|
||||||
|
<button class="btn login-btn" open-type="getPhoneNumber" :disabled="loading" @getphonenumber="onGetPhoneNumber">
|
||||||
|
<text class="btn-text">🚀 微信一键加入</text>
|
||||||
|
<view class="btn-shine"></view>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- #ifndef MP-WEIXIN -->
|
||||||
|
<view class="action-section">
|
||||||
|
<button class="btn login-btn" @click="goLogin">
|
||||||
|
<text class="btn-text">🚀 立即加入</text>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- 协议区 -->
|
||||||
|
<view class="agreements">
|
||||||
|
<view class="checkbox-area" @click="toggleAgreement">
|
||||||
|
<view class="checkbox round" :class="{ checked: agreementChecked }">
|
||||||
|
<view class="check-mark" v-if="agreementChecked">✓</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="agreement-text">
|
||||||
|
登录即代表同意 <text class="link" @tap="toUserAgreement">用户协议</text> & <text class="link" @tap="toPurchaseAgreement">隐私政策</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="error" class="error-toast">{{ error }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { wechatLogin, bindPhone, getUserStats, getPointsBalance } from '../../api/appUser'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref('')
|
||||||
|
const inviteCode = ref('')
|
||||||
|
const agreementChecked = ref(false)
|
||||||
|
|
||||||
|
onLoad((options) => {
|
||||||
|
const code = options.invite_code || options.inviteCode || ''
|
||||||
|
if (code) {
|
||||||
|
inviteCode.value = code
|
||||||
|
uni.setStorageSync('inviter_code', code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function toggleAgreement() {
|
||||||
|
agreementChecked.value = !agreementChecked.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function toUserAgreement() { uni.navigateTo({ url: '/pages/agreement/user' }) }
|
||||||
|
function toPurchaseAgreement() { uni.navigateTo({ url: '/pages/agreement/purchase' }) }
|
||||||
|
|
||||||
|
function goLogin() {
|
||||||
|
uni.navigateTo({ url: '/pages/login/index' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function onGetPhoneNumber(e) {
|
||||||
|
if (!agreementChecked.value) {
|
||||||
|
uni.showToast({ title: '请先阅读并同意协议', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const phoneCode = e.detail.code
|
||||||
|
if (!phoneCode) {
|
||||||
|
uni.showToast({ title: '未授权手机号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
|
||||||
|
uni.login({
|
||||||
|
provider: 'weixin',
|
||||||
|
success: async (res) => {
|
||||||
|
try {
|
||||||
|
const loginCode = res.code
|
||||||
|
const inviterCode = uni.getStorageSync('inviter_code')
|
||||||
|
const data = await wechatLogin(loginCode, inviterCode)
|
||||||
|
const token = data && data.token
|
||||||
|
const user_id = data && data.user_id
|
||||||
|
const user_info = data || {}
|
||||||
|
|
||||||
|
uni.setStorageSync('user_info', user_info)
|
||||||
|
if (token) uni.setStorageSync('token', token)
|
||||||
|
if (user_id) uni.setStorageSync('user_id', user_id)
|
||||||
|
if (user_info.avatar) uni.setStorageSync('avatar', user_info.avatar)
|
||||||
|
if (user_info.nickname) uni.setStorageSync('nickname', user_info.nickname)
|
||||||
|
if (user_info.invite_code) uni.setStorageSync('invite_code', user_info.invite_code)
|
||||||
|
const openid = data && (data.openid || data.open_id)
|
||||||
|
if (openid) uni.setStorageSync('openid', openid)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise(r => setTimeout(r, 600))
|
||||||
|
const bindRes = await bindPhone(user_id, phoneCode, { 'X-Suppress-Auth-Modal': true })
|
||||||
|
const phoneNumber = (bindRes && (bindRes.phone || bindRes.phone_number || bindRes.mobile)) || ''
|
||||||
|
if (phoneNumber) uni.setStorageSync('phone_number', phoneNumber)
|
||||||
|
} catch (bindErr) {
|
||||||
|
console.warn('Bind phone failed', bindErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.setStorageSync('phone_bound', true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = await getUserStats(user_id)
|
||||||
|
uni.setStorageSync('user_stats', stats)
|
||||||
|
const balance = await getPointsBalance(user_id)
|
||||||
|
const b = balance && balance.balance !== undefined ? balance.balance : balance
|
||||||
|
uni.setStorageSync('points_balance', b)
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
|
uni.showToast({ title: '欢迎加入!', icon: 'success' })
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.switchTab({ url: '/pages/index/index' })
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err.message || '登录失败'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
error.value = '微信登录失败'
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: $bg-secondary;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 装饰光球 - 与登录页保持一致 */
|
||||||
|
.orb {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(80rpx);
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.orb-1 {
|
||||||
|
width: 500rpx;
|
||||||
|
height: 500rpx;
|
||||||
|
background: radial-gradient(circle, rgba($brand-primary, 0.4), transparent 70%);
|
||||||
|
top: -100rpx;
|
||||||
|
left: -100rpx;
|
||||||
|
animation: float 8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.orb-2 {
|
||||||
|
width: 600rpx;
|
||||||
|
height: 600rpx;
|
||||||
|
background: radial-gradient(circle, rgba($accent-gold, 0.3), transparent 70%);
|
||||||
|
bottom: -150rpx;
|
||||||
|
right: -150rpx;
|
||||||
|
animation: float 10s ease-in-out infinite reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translate(0, 0); }
|
||||||
|
50% { transform: translate(30rpx, 40rpx); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrap {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 40rpx;
|
||||||
|
padding-top: calc(env(safe-area-inset-top) + 40rpx);
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from { opacity: 0; transform: translateY(40rpx); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero Card */
|
||||||
|
.hero-card {
|
||||||
|
padding: 60rpx 40rpx;
|
||||||
|
margin-bottom: $spacing-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-box {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
background: $bg-card;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
box-shadow: 0 12rpx 30rpx rgba($brand-primary, 0.2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: $spacing-xl;
|
||||||
|
animation: pulse 3s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); box-shadow: 0 12rpx 30rpx rgba($brand-primary, 0.2); }
|
||||||
|
50% { transform: scale(1.02); box-shadow: 0 16rpx 40rpx rgba($brand-primary, 0.3); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 44rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: $text-main;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
text-shadow: 0 2rpx 4rpx rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: $text-sub;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 邀请人信息 */
|
||||||
|
.invite-info {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba($brand-primary, 0.08);
|
||||||
|
border-radius: $radius-lg;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
border: 1rpx solid rgba($brand-primary, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-emoji {
|
||||||
|
font-size: 48rpx;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-main {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-main;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-code {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $text-sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Benefits Card */
|
||||||
|
.benefits-card {
|
||||||
|
padding: 40rpx;
|
||||||
|
margin-bottom: $spacing-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $text-main;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: $bg-card;
|
||||||
|
border-radius: $radius-lg;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-shadow: $shadow-sm;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-icon-wrap {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
background: rgba($brand-primary, 0.1);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-icon {
|
||||||
|
font-size: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-main {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-main;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-sub {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: $text-sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Section */
|
||||||
|
.action-section {
|
||||||
|
margin-bottom: $spacing-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
height: 96rpx;
|
||||||
|
border-radius: $radius-round;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: $font-lg;
|
||||||
|
font-weight: 800;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:active { transform: scale(0.96); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
|
background: $gradient-brand;
|
||||||
|
color: $text-inverse;
|
||||||
|
box-shadow: 0 10rpx 30rpx rgba($brand-primary, 0.3);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-shine {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: -100%;
|
||||||
|
width: 50%; height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
|
||||||
|
transform: skewX(-20deg);
|
||||||
|
animation: shine 3s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shine {
|
||||||
|
0% { left: -100%; }
|
||||||
|
20% { left: 200%; }
|
||||||
|
100% { left: 200%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Agreements */
|
||||||
|
.agreements {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-area {
|
||||||
|
padding-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border: 3rpx solid $border-color;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
background: rgba(255,255,255,0.5);
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
background: $brand-primary;
|
||||||
|
border-color: $brand-primary;
|
||||||
|
box-shadow: 0 4rpx 10rpx rgba($brand-primary, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-mark {
|
||||||
|
color: $text-inverse;
|
||||||
|
font-size: $font-sm;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-text {
|
||||||
|
font-size: $font-sm;
|
||||||
|
color: $text-tertiary;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: $brand-primary;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 100rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: rgba($uni-color-error, 0.9);
|
||||||
|
color: $text-inverse;
|
||||||
|
padding: 16rpx 32rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
z-index: 999;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.2);
|
||||||
|
animation: slideDown 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from { transform: translate(-50%, -100%); opacity: 0; }
|
||||||
|
to { transform: translate(-50%, 0); opacity: 1; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -61,10 +61,6 @@
|
|||||||
<button class="btn test-login-btn" @click="handleTestLogin">
|
<button class="btn test-login-btn" @click="handleTestLogin">
|
||||||
<text class="btn-text">测试账号登录 (Dev)</text>
|
<text class="btn-text">测试账号登录 (Dev)</text>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<view class="register-link">
|
|
||||||
<text class="register-text" @click="goToRegister">没有账号?<text class="highlight">立即注册</text></text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
|
||||||
@ -126,8 +122,6 @@ function toggleAgreement() {
|
|||||||
agreementChecked.value = !agreementChecked.value
|
agreementChecked.value = !agreementChecked.value
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToRegister() { uni.navigateTo({ url: '/pages/register/register' }) }
|
|
||||||
|
|
||||||
function handleLogin() {
|
function handleLogin() {
|
||||||
if (!agreementChecked.value) {
|
if (!agreementChecked.value) {
|
||||||
uni.showToast({ title: '请先阅读并同意协议', icon: 'none' })
|
uni.showToast({ title: '请先阅读并同意协议', icon: 'none' })
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
<!-- 数据统计栏 -->
|
<!-- 数据统计栏 -->
|
||||||
<view class="stats-row">
|
<view class="stats-row">
|
||||||
<view class="stat-item" @click="toPointsPage">
|
<view class="stat-item" @click="toPointsPage">
|
||||||
<text class="stat-num">{{ pointsBalance || 0 }}</text>
|
<text class="stat-num">{{ formatPoints(pointsBalance) }}</text>
|
||||||
<text class="stat-label">积分</text>
|
<text class="stat-label">积分</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-divider"></view>
|
<view class="stat-divider"></view>
|
||||||
@ -190,7 +190,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="point-right">
|
<view class="point-right">
|
||||||
<text class="point-amount" :class="{ 'positive': Number(item.points) > 0, 'negative': Number(item.points) < 0 }">
|
<text class="point-amount" :class="{ 'positive': Number(item.points) > 0, 'negative': Number(item.points) < 0 }">
|
||||||
{{ Number(item.points) > 0 ? '+' : '' }}{{ item.points }}
|
{{ Number(item.points) > 0 ? '+' : '' }}{{ formatPoints(item.points) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -516,7 +516,7 @@ export default {
|
|||||||
try { uni.setStorageSync('inviter_code', v) } catch (_) {}
|
try { uni.setStorageSync('inviter_code', v) } catch (_) {}
|
||||||
const token = uni.getStorageSync('token')
|
const token = uni.getStorageSync('token')
|
||||||
if (!token) {
|
if (!token) {
|
||||||
uni.navigateTo({ url: `/pages/register/register?invite_code=${encodeURIComponent(v)}` })
|
uni.navigateTo({ url: '/pages/login/index' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -526,17 +526,17 @@ export default {
|
|||||||
onShareAppMessage() {
|
onShareAppMessage() {
|
||||||
const inviteCode = uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || ''
|
const inviteCode = uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || ''
|
||||||
return {
|
return {
|
||||||
title: '给你一个宝藏应用,快来!',
|
title: '🎁 好友邀请你一起玩,快来领福利!',
|
||||||
path: inviteCode ? `/pages/register/register?invite_code=${inviteCode}` : '/pages/register/register',
|
path: inviteCode ? `/pages/invite/landing?invite_code=${inviteCode}` : '/pages/invite/landing',
|
||||||
imageUrl: '/static/logo.png'
|
imageUrl: '/static/share_invite.png'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShareTimeline() {
|
onShareTimeline() {
|
||||||
const inviteCode = uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || ''
|
const inviteCode = uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || ''
|
||||||
return {
|
return {
|
||||||
title: '给你一个宝藏应用,快来!',
|
title: '🎁 好友邀请你一起玩,快来领福利!',
|
||||||
query: inviteCode ? `invite_code=${inviteCode}` : '',
|
query: inviteCode ? `invite_code=${inviteCode}` : '',
|
||||||
imageUrl: '/static/logo.png'
|
imageUrl: '/static/share_invite.png'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -544,6 +544,12 @@ export default {
|
|||||||
const v = this.inviteCode || uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || ''
|
const v = this.inviteCode || uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || ''
|
||||||
return String(v || '').trim()
|
return String(v || '').trim()
|
||||||
},
|
},
|
||||||
|
formatPoints(v) {
|
||||||
|
const n = Number(v) || 0
|
||||||
|
if (n === 0) return '0'
|
||||||
|
const f = n / 100
|
||||||
|
return Number.isInteger(f) ? String(f) : f.toFixed(2).replace(/\.?0+$/, '')
|
||||||
|
},
|
||||||
copyInviteCode() {
|
copyInviteCode() {
|
||||||
const code = this.getInviteCode()
|
const code = this.getInviteCode()
|
||||||
if (!code) {
|
if (!code) {
|
||||||
@ -559,7 +565,7 @@ export default {
|
|||||||
},
|
},
|
||||||
getInviteSharePath() {
|
getInviteSharePath() {
|
||||||
const code = this.getInviteCode()
|
const code = this.getInviteCode()
|
||||||
return code ? `/pages/register/register?invite_code=${encodeURIComponent(code)}` : '/pages/register/register'
|
return code ? `/pages/invite/landing?invite_code=${encodeURIComponent(code)}` : '/pages/invite/landing'
|
||||||
},
|
},
|
||||||
normalizePointsBalance(v) {
|
normalizePointsBalance(v) {
|
||||||
if (v && typeof v === 'object') {
|
if (v && typeof v === 'object') {
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
<view class="record-main">
|
<view class="record-main">
|
||||||
<view class="record-title">{{ getActionText(item.action) || item.title || item.reason || '积分变更' }}</view>
|
<view class="record-title">{{ getActionText(item.action) || item.title || item.reason || '积分变更' }}</view>
|
||||||
<view class="record-amount" :class="{ inc: (item.points || 0) > 0, dec: (item.points || 0) < 0 }">
|
<view class="record-amount" :class="{ inc: (item.points || 0) > 0, dec: (item.points || 0) < 0 }">
|
||||||
{{ (item.points ?? 0) > 0 ? '+' : '' }}{{ item.points ?? 0 }}
|
{{ (item.points ?? 0) > 0 ? '+' : '' }}{{ formatPoints(item.points) }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="record-footer">
|
<view class="record-footer">
|
||||||
@ -65,6 +65,12 @@ const error = ref('')
|
|||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const pageSize = ref(20)
|
const pageSize = ref(20)
|
||||||
const hasMore = ref(true)
|
const hasMore = ref(true)
|
||||||
|
function formatPoints(v) {
|
||||||
|
const n = Number(v) || 0
|
||||||
|
if (n === 0) return '0'
|
||||||
|
const f = n / 100
|
||||||
|
return Number.isInteger(f) ? String(f) : f.toFixed(2).replace(/\.?0+$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
function formatTime(t) {
|
function formatTime(t) {
|
||||||
if (!t) return ''
|
if (!t) return ''
|
||||||
|
|||||||
@ -1,172 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="container">
|
|
||||||
<image class="logo" src="/static/logo.png" mode="widthFix"></image>
|
|
||||||
<view class="title">注册新账号</view>
|
|
||||||
|
|
||||||
<view class="form">
|
|
||||||
<view class="input-row">
|
|
||||||
<text class="label">邀请码</text>
|
|
||||||
<input type="text" v-model="inviteCode" class="input-field" placeholder="请输入邀请码" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="input-row">
|
|
||||||
<text class="label">账号</text>
|
|
||||||
<input type="text" v-model="account" class="input-field" placeholder="请输入账号" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="input-row">
|
|
||||||
<text class="label">密码</text>
|
|
||||||
<input type="password" v-model="password" class="input-field" placeholder="请输入密码" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="input-row">
|
|
||||||
<text class="label">确认密码</text>
|
|
||||||
<input type="password" v-model="confirmPassword" class="input-field" placeholder="请再次输入密码" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<button class="btn submit-btn" :disabled="loading" @click="onRegister">注册</button>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="login-link">
|
|
||||||
<text @tap="goLogin">已有账号?去登录</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="error" class="error">{{ error }}</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
|
||||||
|
|
||||||
const account = ref('')
|
|
||||||
const password = ref('')
|
|
||||||
const confirmPassword = ref('')
|
|
||||||
const inviteCode = ref('')
|
|
||||||
const loading = ref(false)
|
|
||||||
const error = ref('')
|
|
||||||
|
|
||||||
onLoad((opts) => {
|
|
||||||
const code = (opts && (opts.invite_code || opts.inviteCode)) || ''
|
|
||||||
const v = String(code || uni.getStorageSync('inviter_code') || '').trim()
|
|
||||||
if (v) {
|
|
||||||
inviteCode.value = v
|
|
||||||
try { uni.setStorageSync('inviter_code', v) } catch (_) {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function goLogin() {
|
|
||||||
uni.navigateBack()
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRegister() {
|
|
||||||
if (!account.value || !password.value) {
|
|
||||||
uni.showToast({ title: '请填写完整', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (password.value !== confirmPassword.value) {
|
|
||||||
uni.showToast({ title: '两次密码不一致', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: 调用注册 API
|
|
||||||
uni.showToast({ title: '注册功能开发中', icon: 'none' })
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* ============================================
|
|
||||||
柯大鸭潮玩 - 注册页面
|
|
||||||
============================================ */
|
|
||||||
|
|
||||||
.container {
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 60rpx 40rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
background: linear-gradient(180deg, #FFF8F3 0%, #FFE8D1 50%, #FFDAB9 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: 160rpx;
|
|
||||||
height: 160rpx;
|
|
||||||
margin-top: 80rpx;
|
|
||||||
margin-bottom: 32rpx;
|
|
||||||
border-radius: 32rpx;
|
|
||||||
box-shadow: 0 12rpx 36rpx rgba(255, 107, 53, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 40rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1F2937;
|
|
||||||
margin-bottom: 48rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 600rpx;
|
|
||||||
background: rgba(255, 255, 255, 0.85);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
border-radius: 32rpx;
|
|
||||||
padding: 40rpx;
|
|
||||||
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
background: #F9FAFB;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 8rpx 24rpx;
|
|
||||||
border: 2rpx solid #E5E7EB;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
width: 140rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #6B7280;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-field {
|
|
||||||
flex: 1;
|
|
||||||
height: 80rpx;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #1F2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn {
|
|
||||||
width: 100%;
|
|
||||||
height: 88rpx;
|
|
||||||
line-height: 88rpx;
|
|
||||||
margin-top: 32rpx;
|
|
||||||
background: linear-gradient(135deg, #FF9F43, #FF6B35) !important;
|
|
||||||
color: #FFFFFF !important;
|
|
||||||
border: none;
|
|
||||||
border-radius: 44rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.35);
|
|
||||||
}
|
|
||||||
.submit-btn:active {
|
|
||||||
transform: scale(0.97);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-link {
|
|
||||||
margin-top: 32rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #FF9F43;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
margin-top: 24rpx;
|
|
||||||
color: #EF4444;
|
|
||||||
font-size: 26rpx;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
BIN
static/share_invite.png
Normal file
BIN
static/share_invite.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 518 KiB |
@ -22,7 +22,6 @@ function handleAuthExpired() {
|
|||||||
export function request({ url, method = 'GET', data = {}, header = {} }) {
|
export function request({ url, method = 'GET', data = {}, header = {} }) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const finalHeader = { ...buildDefaultHeaders(), ...header }
|
const finalHeader = { ...buildDefaultHeaders(), ...header }
|
||||||
try { console.log('HTTP request', method, url, 'data', data, 'headers', finalHeader) } catch (e) { }
|
|
||||||
uni.request({
|
uni.request({
|
||||||
url: BASE_URL + url,
|
url: BASE_URL + url,
|
||||||
method,
|
method,
|
||||||
@ -31,7 +30,6 @@ export function request({ url, method = 'GET', data = {}, header = {} }) {
|
|||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const code = res.statusCode
|
const code = res.statusCode
|
||||||
try { console.log('HTTP response', method, url, 'status', code, 'body', res.data) } catch (e) { }
|
|
||||||
if (code >= 200 && code < 300) {
|
if (code >= 200 && code < 300) {
|
||||||
const body = res.data
|
const body = res.data
|
||||||
resolve(body && body.data !== undefined ? body.data : body)
|
resolve(body && body.data !== undefined ? body.data : body)
|
||||||
@ -47,7 +45,6 @@ export function request({ url, method = 'GET', data = {}, header = {} }) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
try { console.error('HTTP fail', method, url, 'err', err) } catch (e) { }
|
|
||||||
reject(err)
|
reject(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user