feat: 优化活动奖励图片处理、登录流程及Authorization头设置,并改进对对碰活动奖励展示和排序逻辑

This commit is contained in:
邹方成 2025-12-28 22:48:28 +08:00
parent d1fd76e242
commit 0bd10c6a0d
6 changed files with 117 additions and 27 deletions

View File

@ -76,6 +76,32 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'close'])
function cleanUrl(u) {
if (!u) return '/static/logo.png'
let s = String(u).trim()
// JSON ( JSON )
if (s.startsWith('[') && s.endsWith(']')) {
try {
const arr = JSON.parse(s)
if (Array.isArray(arr) && arr.length > 0) {
s = arr[0]
}
} catch (e) {
console.warn('JSON parse failed for prize image:', s)
}
}
//
s = s.replace(/[`'"]/g, '').trim()
// http
const m = s.match(/https?:\/\/[^\s]+/)
if (m && m[0]) return m[0]
return s || '/static/logo.png'
}
const groupedResults = computed(() => {
const map = new Map()
const arr = Array.isArray(props.results) ? props.results : []
@ -90,7 +116,7 @@ const groupedResults = computed(() => {
} else {
map.set(key, {
title: item.title || item.name || '神秘奖品',
image: item.image || item.img || item.pic || '',
image: cleanUrl(item.image || item.img || item.pic || ''),
reward_id: rewardId,
quantity: 1
})

View File

@ -14,7 +14,7 @@
</view>
<scroll-view class="preview-scroll" scroll-x>
<view class="preview-item" v-for="(item, idx) in group.rewards" :key="item.id || idx">
<view class="prize-tag" :class="{ 'tag-boss': item.boss }">{{ item.boss ? 'BOSS' : group.level }}</view>
<view class="prize-tag tag-boss" v-if="item.boss">BOSS</view>
<image class="preview-img" :src="item.image" mode="aspectFill" />
<view class="preview-name">{{ item.title }}</view>
</view>

View File

@ -317,13 +317,21 @@ const currentIssueRewards = computed(() => {
const rewardGroups = computed(() => {
const groups = {}
currentIssueRewards.value.forEach(item => {
const level = item.level || '赏'
let level = item.level || '赏'
if (item.min_score > 0 && level !== 'BOSS') {
level = `${item.min_score}对子`
}
if (!groups[level]) groups[level] = []
groups[level].push(item)
})
return Object.keys(groups).sort((a, b) => {
if (a === 'BOSS') return -1
if (b === 'BOSS') return 1
if (a === 'Last' || a === 'BOSS') return -1
if (b === 'Last' || b === 'BOSS') return 1
const numA = parseInt(a)
const numB = parseInt(b)
if (!isNaN(numA) && !isNaN(numB)) {
return numB - numA
}
return a.localeCompare(b)
}).map(key => {
const rewards = groups[key]
@ -506,11 +514,13 @@ function normalizeIssues(list) {
function normalizeRewards(list) {
const arr = unwrap(list)
const items = arr.map((i, idx) => ({
...i, // Spread original properties first
id: i.product_id ?? i.id ?? String(idx),
title: i.name ?? i.title ?? '',
image: cleanUrl(i.product_image ?? i.image ?? i.img ?? i.pic ?? i.banner ?? ''),
weight: Number(i.weight) || 0,
boss: detectBoss(i),
min_score: Number(i.min_score) || 0, // Extract min_score
level: levelToAlpha(i.prize_level ?? i.level ?? (detectBoss(i) ? 'BOSS' : '赏'))
}))
const total = items.reduce((acc, it) => acc + (it.weight > 0 ? it.weight : 0), 0)
@ -518,7 +528,8 @@ function normalizeRewards(list) {
...it,
percent: total > 0 ? Math.round((it.weight / total) * 1000) / 10 : 0
}))
enriched.sort((a, b) => (b.percent - a.percent))
//
// enriched.sort((a, b) => (b.percent - a.percent))
return enriched
}
async function fetchRewardsForIssues(activityId) {
@ -974,7 +985,7 @@ async function finishAndReport() {
)
return {
title: item.title || item.name || found?.name || found?.title || '神秘奖励',
image: item.image || item.img || found?.image || found?.pic || found?.product_image || '',
image: cleanUrl(item.image || item.img || found?.image || found?.pic || found?.product_image || ''),
reward_id: item.reward_id || item.id
}
})

View File

@ -22,14 +22,14 @@
:class="{ active: loginMode === 'wechat' }"
@tap="switchMode('wechat')"
>
<text class="tab-text">微信快捷登录</text>
<text class="tab-text">手机号快捷登录</text>
</view>
<view
class="tab-item"
:class="{ active: loginMode === 'sms' }"
@tap="switchMode('sms')"
>
<text class="tab-text">手机号登录</text>
<text class="tab-text">验证码登录</text>
</view>
<view class="tab-indicator" :class="{ right: loginMode === 'sms' }"></view>
</view>
@ -43,8 +43,8 @@
<text class="icon-emoji">💬</text>
</view>
</view>
<text class="panel-title">微信一键授权</text>
<text class="panel-desc">使用微信授权获取手机号安全快速</text>
<text class="panel-title">一键获取手机号</text>
<text class="panel-desc">授权获取本机手机号安全快速登录</text>
<!-- #ifdef MP-WEIXIN -->
<button
@ -53,13 +53,13 @@
:disabled="loading"
@getphonenumber="onGetPhoneNumber"
>
{{ loading ? '授权中...' : '微信授权登录' }}
{{ loading ? '获取中...' : '一键获取手机号' }}
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<button class="btn-primary btn-login" disabled>
请在微信小程序中使用
当前环境不支持
</button>
<!-- #endif -->
</view>
@ -146,6 +146,8 @@
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { request } from '../../utils/request'
import { wechatLogin, bindPhone, getUserStats, getPointsBalance, sendSmsCode, smsLogin } from '../../api/appUser'
const loading = ref(false)
@ -154,6 +156,38 @@ const agreementChecked = ref(false)
//
const loginMode = ref('wechat')
// OpenID
async function ensureOpenID() {
const current = uni.getStorageSync('openid')
if (current) return
console.log('[DEBUG] 本地缺少 openid, 尝试静默获取...')
uni.login({
provider: 'weixin',
success: async (loginRes) => {
try {
const res = await request({
url: '/api/app/common/openid',
method: 'POST',
data: { code: loginRes.code }
})
if (res && res.openid) {
console.log('[DEBUG] 静默获取 openid 成功:', res.openid)
uni.setStorageSync('openid', res.openid)
}
} catch (err) {
console.error('[DEBUG] 静默获取 openid 失败:', err)
}
}
})
}
onLoad(() => {
ensureOpenID()
})
const fetchExtraDataLoading = ref(false)
//
const mobile = ref('')
const smsCode = ref('')
@ -254,6 +288,7 @@ async function handleSmsLogin() {
const inviterCode = uni.getStorageSync('inviter_code')
const data = await smsLogin(mobile.value, smsCode.value, inviterCode)
console.log('[DEBUG] 短信登录响应原始数据:', data)
saveUserData(data)
const isNew = data && data.is_new_user
@ -301,18 +336,12 @@ function onGetPhoneNumber(e) {
saveUserData(data)
const openid = data && (data.openid || data.open_id)
if (openid) uni.setStorageSync('openid', openid)
//
// ()
const isBound = data.phone || data.phone_number || data.mobile
if (!isBound) {
try {
await bindPhone(data.user_id, phoneCode)
uni.setStorageSync('phone_bound', true)
} catch (e) {}
} else {
uni.setStorageSync('phone_bound', true)
}
//
@ -334,6 +363,7 @@ function onGetPhoneNumber(e) {
function saveUserData(data) {
if (!data) return
console.log('[DEBUG] 准备执行 saveUserData, payload:', data)
uni.setStorageSync('user_info', data)
if (data.token) uni.setStorageSync('token', data.token)
if (data.user_id) uni.setStorageSync('user_id', data.user_id)
@ -341,6 +371,18 @@ function saveUserData(data) {
if (data.nickname) uni.setStorageSync('nickname', data.nickname)
if (data.invite_code) uni.setStorageSync('invite_code', data.invite_code)
if (data.mobile) uni.setStorageSync('last_login_mobile', data.mobile)
//
uni.setStorageSync('phone_bound', true)
// openid ( openid)
const openid = data.openid || data.open_id
if (openid) {
console.log('[DEBUG] 存储从登录接口获取的 openid:', openid)
uni.setStorageSync('openid', openid)
} else {
console.warn('[DEBUG] 登录接口未返回 openid, 请检查后端或联系管理员')
}
}
function fetchExtraData(userId) {

View File

@ -109,6 +109,7 @@ export function normalizeRewards(list, cleanUrl = (u) => u) {
image: cleanUrl(i.product_image ?? i.image ?? i.img ?? i.pic ?? i.banner ?? ''),
weight: Number(i.weight) || 0,
boss: detectBoss(i),
min_score: i.min_score || 0,
level: levelToAlpha(i.prize_level ?? i.level ?? (detectBoss(i) ? 'BOSS' : '赏'))
}))
const total = items.reduce((acc, it) => acc + (it.weight > 0 ? it.weight : 0), 0)
@ -119,7 +120,8 @@ export function normalizeRewards(list, cleanUrl = (u) => u) {
percent: parseFloat(rawPercent.toFixed(2)) // 统一保留2位小数
}
})
enriched.sort((a, b) => (b.percent - a.percent))
// 移除前端按百分比强制排序逻辑,保留后端原始排序
// enriched.sort((a, b) => (b.percent - a.percent))
return enriched
}
@ -150,13 +152,23 @@ export function pickLatestIssueId(list) {
export function groupRewardsByLevel(rewards) {
const groups = {}
; (rewards || []).forEach(item => {
const level = item.level || '赏'
let level = item.level || '赏'
// 如果是对对碰(具有 min_score 且不是 BOSS则组名包含对子数
if (item.min_score > 0 && level !== 'BOSS') {
level = `${item.min_score}对子`
}
if (!groups[level]) groups[level] = []
groups[level].push(item)
})
return Object.keys(groups).sort((a, b) => {
if (a === 'BOSS') return -1
if (b === 'BOSS') return 1
if (a === 'Last' || a === 'BOSS') return -1
if (b === 'Last' || b === 'BOSS') return 1
// 数字对照组(比如 "9对子")需要按数字降序排列
const numA = parseInt(a)
const numB = parseInt(b)
if (!isNaN(numA) && !isNaN(numB)) {
return numB - numA
}
return a.localeCompare(b)
}).map(key => {
const levelRewards = groups[key]
@ -164,7 +176,7 @@ export function groupRewardsByLevel(rewards) {
return {
level: key,
rewards: levelRewards,
totalPercent: total.toFixed(2) // 统一保留2位小数
totalPercent: total.toFixed(2)
}
})
}

View File

@ -55,10 +55,9 @@ export function authRequest(options) {
const token = uni.getStorageSync('token')
const base = buildDefaultHeaders()
const header = { ...base, ...(options.header || {}) }
// 不再添加 Bearer直接原样透传 token
// 设置Authorization头
if (token) {
header.Authorization = token
header.authorization = token
}
return request({ ...options, header })
}