更新日志,一番赏移除道具卡
This commit is contained in:
parent
8f044d68ca
commit
b79cd37932
8
App.vue
8
App.vue
@ -1,8 +1,12 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
onLaunch: function() {
|
onLaunch: function(options) {
|
||||||
console.log('App Launch')
|
console.log('App Launch', options)
|
||||||
try { uni.setStorageSync('app_session_id', String(Date.now())) } catch (_) {}
|
try { uni.setStorageSync('app_session_id', String(Date.now())) } catch (_) {}
|
||||||
|
if (options && options.query && options.query.invite_code) {
|
||||||
|
console.log('App Launch captured invite_code:', options.query.invite_code)
|
||||||
|
try { uni.setStorageSync('inviter_code', options.query.invite_code) } catch (e) { console.error('Save invite code failed', e) }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onShow: function() {
|
onShow: function() {
|
||||||
console.log('App Show')
|
console.log('App Show')
|
||||||
|
|||||||
@ -5,6 +5,10 @@ export function wechatLogin(code, invite_code) {
|
|||||||
return request({ url: '/api/app/users/weixin/login', method: 'POST', data })
|
return request({ url: '/api/app/users/weixin/login', method: 'POST', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getInventory(user_id, page = 1, page_size = 20){
|
||||||
|
return authRequest({ url: `/api/app/users/${user_id}/inventory`, method: 'GET', data: { page, page_size } })
|
||||||
|
}
|
||||||
|
|
||||||
export function bindPhone(user_id, code, extraHeader = {}) {
|
export function bindPhone(user_id, code, extraHeader = {}) {
|
||||||
return authRequest({ url: `/api/app/users/${user_id}/phone/bind`, method: 'POST', data: { code }, header: extraHeader })
|
return authRequest({ url: `/api/app/users/${user_id}/phone/bind`, method: 'POST', data: { code }, header: extraHeader })
|
||||||
}
|
}
|
||||||
@ -66,3 +70,61 @@ export function drawActivityIssue(activity_id, issue_id) {
|
|||||||
export function getActivityWinRecords(activity_id, page = 1, page_size = 20) {
|
export function getActivityWinRecords(activity_id, page = 1, page_size = 20) {
|
||||||
return authRequest({ url: `/api/app/activities/${activity_id}/wins`, method: 'GET', data: { page, page_size } })
|
return authRequest({ url: `/api/app/activities/${activity_id}/wins`, method: 'GET', data: { page, page_size } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getIssueChoices(activity_id, issue_id) {
|
||||||
|
return authRequest({ url: `/api/app/activities/${activity_id}/issues/${issue_id}/choices`, method: 'GET' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProductDetail(product_id) {
|
||||||
|
return authRequest({ url: `/api/app/products/${product_id}`, method: 'GET' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function redeemInventory(user_id, ids) {
|
||||||
|
return authRequest({ url: `/api/app/users/${user_id}/inventory/redeem`, method: 'POST', data: { inventory_ids: ids } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requestShipping(user_id, ids) {
|
||||||
|
return authRequest({ url: `/api/app/users/${user_id}/inventory/request-shipping`, method: 'POST', data: { inventory_ids: ids } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getItemCards(user_id) {
|
||||||
|
return authRequest({ url: `/api/app/users/${user_id}/item_cards`, method: 'GET' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserCoupons(user_id, status, page = 1, page_size = 20) {
|
||||||
|
const data = { page, page_size }
|
||||||
|
if (status !== undefined) data.status = status
|
||||||
|
return authRequest({ url: `/api/app/users/${user_id}/coupons`, method: 'GET', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function redeemCoupon(user_id, code) {
|
||||||
|
return authRequest({ url: `/api/app/users/${user_id}/coupons/redeem`, method: 'POST', data: { code } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function joinLottery(data) {
|
||||||
|
return authRequest({ url: '/api/app/lottery/join', method: 'POST', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWechatOrder(data) {
|
||||||
|
return authRequest({ url: '/api/app/pay/wechat/jsapi/preorder', method: 'POST', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLotteryResult(order_no) {
|
||||||
|
return authRequest({ url: '/api/app/lottery/result', method: 'GET', data: { order_no } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserPoints(user_id, page = 1, page_size = 20) {
|
||||||
|
return authRequest({ url: `/api/app/users/${user_id}/points`, method: 'GET', data: { page, page_size } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function redeemProductByPoints(user_id, product_id, quantity) {
|
||||||
|
return authRequest({ url: `/api/app/users/${user_id}/points/redeem-product`, method: 'POST', data: { product_id, quantity } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTasks(page = 1, page_size = 20) {
|
||||||
|
return authRequest({ url: '/api/app/task-center/tasks', method: 'GET', data: { page, page_size } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getShipments(user_id, page = 1, page_size = 20) {
|
||||||
|
return authRequest({ url: `/api/app/users/${user_id}/shipments`, method: 'GET', data: { page, page_size } })
|
||||||
|
}
|
||||||
|
|||||||
@ -1,146 +0,0 @@
|
|||||||
uni.api.esm.js:502 App Launch
|
|
||||||
uni.api.esm.js:502 App Show
|
|
||||||
VM75:363 Error: not node js file system!path:/saaa_config.json; go __invokeHandler__ readFile worker? false
|
|
||||||
h @ VM75:363
|
|
||||||
a.WeixinJSCore.invokeHandler @ VM75:363
|
|
||||||
b @ WAServiceMainContext.js:1
|
|
||||||
invoke @ WAServiceMainContext.js:1
|
|
||||||
invoke @ WAServiceMainContext.js:1
|
|
||||||
u @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
ae @ WAServiceMainContext.js:1
|
|
||||||
o @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
QX @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
c.emit @ WAServiceMainContext.js:1
|
|
||||||
emitInternal @ WAServiceMainContext.js:1
|
|
||||||
e @ WAServiceMainContext.js:1
|
|
||||||
c.emit @ WAServiceMainContext.js:1
|
|
||||||
emitInternal @ WAServiceMainContext.js:1
|
|
||||||
p @ WAServiceMainContext.js:1
|
|
||||||
_ @ WAServiceMainContext.js:1
|
|
||||||
B @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
a @ WAServiceMainContext.js:1
|
|
||||||
s @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
c.emit @ WAServiceMainContext.js:1
|
|
||||||
emit @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
_emit @ WAServiceMainContext.js:1
|
|
||||||
emit @ WAServiceMainContext.js:1
|
|
||||||
emit @ WAServiceMainContext.js:1
|
|
||||||
subscribeHandler @ WAServiceMainContext.js:1
|
|
||||||
ret.subscribeHandler
|
|
||||||
Show 8 more frames
|
|
||||||
uni.api.esm.js:502 mine onShow token: isLogin: false phoneBound: false
|
|
||||||
uni.api.esm.js:502 mine login modal confirm: true
|
|
||||||
WAServiceMainContext.js:1 [wxapplib]] backgroundfetch privacy fail {"errno":101,"errMsg":"private_getBackgroundFetchData:fail private_getBackgroundFetchData:fail:jsapi invalid request data"}
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
i @ WAServiceMainContext.js:1
|
|
||||||
s @ WAServiceMainContext.js:1
|
|
||||||
fail @ WAServiceMainContext.js:1
|
|
||||||
p @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
u @ WAServiceMainContext.js:1
|
|
||||||
fail @ WAServiceMainContext.js:1
|
|
||||||
p @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
G @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
r @ WAServiceMainContext.js:1
|
|
||||||
s @ WAServiceMainContext.js:1
|
|
||||||
callAndRemove @ WAServiceMainContext.js:1
|
|
||||||
invokeCallbackHandler @ WAServiceMainContext.js:1
|
|
||||||
ret.invokeCallbackHandler
|
|
||||||
uni.api.esm.js:502 login_flow start getPhoneNumber, codeExists: true
|
|
||||||
uni.api.esm.js:502 login_flow uni.login success, loginCode exists: true
|
|
||||||
uni.api.esm.js:502 HTTP request POST /api/app/users/weixin/login data {code: "0f1tQZFa1a5TFK0U6UGa11gIyJ0tQZF6"} headers {Accept: "application/json", content-type: "application/json", X-Requested-With: "XMLHttpRequest", Accept-Language: "zh_CN", X-App-Client: "uni-app", …}
|
|
||||||
uni.api.esm.js:502 HTTP response POST /api/app/users/weixin/login status 200 body {user_id: 813, nickname: "善良的巴乔", avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAA…BNCqAnHlGBRgAAQAA//8HSv0f1XCh/AAAAABJRU5ErkJggg==", invite_code: "AZCHW75Z", token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ODEzL…MzNH0.ZUItPpkDEbAhTy8g80PGRxLxQ_JXqS4jlu_vz7IrMAU"}avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAABmAADpPQ5dAAAAAXRSTlMAQObYZgAAAD9JREFUeJxiAIH//xHkSBdAEofwRrjAKEAFsMQCTzQjXgCRSMDsES4AoWAALj6iBRABNCqAnHlGBRgAAQAA//8HSv0f1XCh/AAAAABJRU5ErkJggg=="invite_code: "AZCHW75Z"nickname: "善良的巴乔"token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ODEzLCJ1c2VybmFtZSI6IuWWhOiJr-eahOW3tOS5lCIsIm5pY2tuYW1lIjoi5ZaE6Imv55qE5be05LmUIiwiaXNfc3VwZXIiOjAsInBsYXRmb3JtIjoiQVBQIiwiZXhwIjoxNzY1ODc3MzM0LCJuYmYiOjE3NjMyODUzMzQsImlhdCI6MTc2MzI4NTMzNH0.ZUItPpkDEbAhTy8g80PGRxLxQ_JXqS4jlu_vz7IrMAU"user_id: 813[[Prototype]]: Object
|
|
||||||
uni.api.esm.js:502 login_flow wechatLogin response user_id: 813
|
|
||||||
uni.api.esm.js:502 login_flow token stored
|
|
||||||
uni.api.esm.js:502 login_flow user_id stored: 813
|
|
||||||
uni.api.esm.js:502 login_flow bindPhone start
|
|
||||||
uni.api.esm.js:502 HTTP request POST /api/app/users/813/phone/bind data {code: "c568695e007ec9d5dca1ecaf4d5452dd378fd42542d4a90fd9ad7b942b5d4248"} headers {Accept: "application/json", content-type: "application/json", X-Requested-With: "XMLHttpRequest", Accept-Language: "zh_CN", X-App-Client: "uni-app", …}
|
|
||||||
uni.api.esm.js:502 HTTP response POST /api/app/users/813/phone/bind status 401 body {code: 10103, message: "您的账号登录过期,请重新登录。"}
|
|
||||||
uni.api.esm.js:502 login_flow bindPhone 401, try re-login and retry
|
|
||||||
__f__ @ uni.api.esm.js:502
|
|
||||||
Ku.wu.__f__ @ mp.esm.js:523
|
|
||||||
(anonymous) @ index.vue:59
|
|
||||||
s @ app-service.js:1219
|
|
||||||
(anonymous) @ app-service.js:1219
|
|
||||||
(anonymous) @ app-service.js:1219
|
|
||||||
asyncGeneratorStep @ app-service.js:1174
|
|
||||||
i @ app-service.js:1174
|
|
||||||
Promise.then (async)
|
|
||||||
asyncGeneratorStep @ app-service.js:1174
|
|
||||||
c @ app-service.js:1174
|
|
||||||
Promise.then (async)
|
|
||||||
asyncGeneratorStep @ app-service.js:1174
|
|
||||||
c @ app-service.js:1174
|
|
||||||
(anonymous) @ app-service.js:1174
|
|
||||||
(anonymous) @ app-service.js:1174
|
|
||||||
(anonymous) @ index.vue:95
|
|
||||||
I.forEach.v.<computed> @ WAServiceMainContext.js:1
|
|
||||||
p @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
i @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
G @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
r @ WAServiceMainContext.js:1
|
|
||||||
s @ WAServiceMainContext.js:1
|
|
||||||
callAndRemove @ WAServiceMainContext.js:1
|
|
||||||
invokeCallbackHandler @ WAServiceMainContext.js:1
|
|
||||||
ret.invokeCallbackHandler
|
|
||||||
uni.api.esm.js:502 HTTP request POST /api/app/users/weixin/login data {code: "0e15cAll2FVxFg4MxDnl2v9KUj15cAlO"} headers {Accept: "application/json", content-type: "application/json", X-Requested-With: "XMLHttpRequest", Accept-Language: "zh_CN", X-App-Client: "uni-app", …}
|
|
||||||
uni.api.esm.js:502 HTTP response POST /api/app/users/weixin/login status 200 body {user_id: 813, nickname: "善良的巴乔", avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAA…BNCqAnHlGBRgAAQAA//8HSv0f1XCh/AAAAABJRU5ErkJggg==", invite_code: "AZCHW75Z", token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ODEzL…MzNH0.ZUItPpkDEbAhTy8g80PGRxLxQ_JXqS4jlu_vz7IrMAU"}
|
|
||||||
uni.api.esm.js:502 HTTP request POST /api/app/users/813/phone/bind data {code: "c568695e007ec9d5dca1ecaf4d5452dd378fd42542d4a90fd9ad7b942b5d4248"} headers {Accept: "application/json", content-type: "application/json", X-Requested-With: "XMLHttpRequest", Accept-Language: "zh_CN", X-App-Client: "uni-app", …}
|
|
||||||
uni.api.esm.js:502 HTTP response POST /api/app/users/813/phone/bind status 401 body {code: 10103, message: "您的账号登录过期,请重新登录。"}
|
|
||||||
uni.api.esm.js:502 login_flow error: 您的账号登录过期,请重新登录。 status: 401
|
|
||||||
__f__ @ uni.api.esm.js:502
|
|
||||||
Ku.wu.__f__ @ mp.esm.js:523
|
|
||||||
(anonymous) @ index.vue:90
|
|
||||||
s @ app-service.js:1219
|
|
||||||
(anonymous) @ app-service.js:1219
|
|
||||||
(anonymous) @ app-service.js:1219
|
|
||||||
asyncGeneratorStep @ app-service.js:1174
|
|
||||||
i @ app-service.js:1174
|
|
||||||
Promise.then (async)
|
|
||||||
asyncGeneratorStep @ app-service.js:1174
|
|
||||||
c @ app-service.js:1174
|
|
||||||
Promise.then (async)
|
|
||||||
asyncGeneratorStep @ app-service.js:1174
|
|
||||||
c @ app-service.js:1174
|
|
||||||
Promise.then (async)
|
|
||||||
asyncGeneratorStep @ app-service.js:1174
|
|
||||||
i @ app-service.js:1174
|
|
||||||
Promise.then (async)
|
|
||||||
asyncGeneratorStep @ app-service.js:1174
|
|
||||||
c @ app-service.js:1174
|
|
||||||
Promise.then (async)
|
|
||||||
asyncGeneratorStep @ app-service.js:1174
|
|
||||||
c @ app-service.js:1174
|
|
||||||
(anonymous) @ app-service.js:1174
|
|
||||||
(anonymous) @ app-service.js:1174
|
|
||||||
(anonymous) @ index.vue:95
|
|
||||||
I.forEach.v.<computed> @ WAServiceMainContext.js:1
|
|
||||||
p @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
i @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
G @ WAServiceMainContext.js:1
|
|
||||||
(anonymous) @ WAServiceMainContext.js:1
|
|
||||||
r @ WAServiceMainContext.js:1
|
|
||||||
s @ WAServiceMainContext.js:1
|
|
||||||
callAndRemove @ WAServiceMainContext.js:1
|
|
||||||
invokeCallbackHandler @ WAServiceMainContext.js:1
|
|
||||||
ret.invokeCallbackHandler
|
|
||||||
@ -66,7 +66,8 @@
|
|||||||
"usingComponents" : true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"mp-toutiao" : {
|
"mp-toutiao" : {
|
||||||
"usingComponents" : true
|
"usingComponents" : true,
|
||||||
|
"appid" : "ttf031868c6f33d91001"
|
||||||
},
|
},
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : false
|
"enable" : false
|
||||||
|
|||||||
13
pages.json
13
pages.json
@ -21,6 +21,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
,
|
,
|
||||||
|
{
|
||||||
|
"path": "pages/shop/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "商品详情"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,
|
||||||
{
|
{
|
||||||
"path": "pages/cabinet/index",
|
"path": "pages/cabinet/index",
|
||||||
"style": {
|
"style": {
|
||||||
@ -97,6 +104,12 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/activity/duiduipeng/index",
|
"path": "pages/activity/duiduipeng/index",
|
||||||
"style": { "navigationBarTitleText": "对对碰" }
|
"style": { "navigationBarTitleText": "对对碰" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/register/register",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
<template>
|
|
||||||
<scroll-view class="page" scroll-y>
|
|
||||||
<view class="banner" v-if="detail.banner">
|
|
||||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
|
||||||
</view>
|
|
||||||
<view class="header">
|
|
||||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
|
||||||
<view class="meta">分类:{{ detail.category_name || '对对碰' }}</view>
|
|
||||||
<view class="meta" v-if="detail.price_draw !== undefined">参与价:¥{{ detail.price_draw }}</view>
|
|
||||||
<view class="meta" v-if="detail.status !== undefined">状态:{{ statusText }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="actions">
|
|
||||||
<button class="btn" @click="onPreviewBanner">查看图片</button>
|
|
||||||
<button class="btn primary" @click="onParticipate">立即参与</button>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
|
||||||
import { getActivityDetail } from '../../api/appUser'
|
|
||||||
|
|
||||||
const detail = ref({})
|
|
||||||
const statusText = ref('')
|
|
||||||
|
|
||||||
function statusToText(s) {
|
|
||||||
if (s === 1) return '进行中'
|
|
||||||
if (s === 0) return '未开始'
|
|
||||||
if (s === 2) return '已结束'
|
|
||||||
return String(s || '')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchDetail(id) {
|
|
||||||
const data = await getActivityDetail(id)
|
|
||||||
detail.value = data || {}
|
|
||||||
statusText.value = statusToText(detail.value.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPreviewBanner() {
|
|
||||||
const url = detail.value.banner || ''
|
|
||||||
if (url) uni.previewImage({ urls: [url], current: url })
|
|
||||||
}
|
|
||||||
|
|
||||||
function onParticipate() {
|
|
||||||
uni.showToast({ title: '功能待接入', icon: 'none' })
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoad((opts) => {
|
|
||||||
const id = (opts && opts.id) || ''
|
|
||||||
if (id) fetchDetail(id)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.page { height: 100vh }
|
|
||||||
.banner { padding: 24rpx }
|
|
||||||
.banner-img { width: 100% }
|
|
||||||
.header { padding: 0 24rpx }
|
|
||||||
.title { font-size: 36rpx; font-weight: 700 }
|
|
||||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
|
||||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
|
||||||
.btn { flex: 1 }
|
|
||||||
.primary { background-color: #007AFF; color: #fff }
|
|
||||||
</style>
|
|
||||||
@ -5,7 +5,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="header">
|
<view class="header">
|
||||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||||
<view class="meta" v-if="detail.price_draw !== undefined">参与价:¥{{ detail.price_draw }}</view>
|
<view class="meta" v-if="detail.price_draw !== undefined">参与价:¥{{ (Number(detail.price_draw || 0) / 100).toFixed(2) }}</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="issues" v-if="showIssues">
|
<view class="issues" v-if="showIssues">
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
<template>
|
|
||||||
<scroll-view class="page" scroll-y>
|
|
||||||
<view class="banner" v-if="detail.banner">
|
|
||||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
|
||||||
</view>
|
|
||||||
<view class="header">
|
|
||||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
|
||||||
<view class="meta">分类:{{ detail.category_name || '无限赏' }}</view>
|
|
||||||
<view class="meta" v-if="detail.price_draw !== undefined">单次抽选:¥{{ detail.price_draw }}</view>
|
|
||||||
<view class="meta" v-if="detail.status !== undefined">状态:{{ statusText }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="actions">
|
|
||||||
<button class="btn" @click="onPreviewBanner">查看图片</button>
|
|
||||||
<button class="btn primary" @click="onParticipate">立即参与</button>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
|
||||||
import { getActivityDetail } from '../../api/appUser'
|
|
||||||
|
|
||||||
const detail = ref({})
|
|
||||||
const statusText = ref('')
|
|
||||||
|
|
||||||
function statusToText(s) {
|
|
||||||
if (s === 1) return '进行中'
|
|
||||||
if (s === 0) return '未开始'
|
|
||||||
if (s === 2) return '已结束'
|
|
||||||
return String(s || '')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchDetail(id) {
|
|
||||||
const data = await getActivityDetail(id)
|
|
||||||
detail.value = data || {}
|
|
||||||
statusText.value = statusToText(detail.value.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPreviewBanner() {
|
|
||||||
const url = detail.value.banner || ''
|
|
||||||
if (url) uni.previewImage({ urls: [url], current: url })
|
|
||||||
}
|
|
||||||
|
|
||||||
function onParticipate() {
|
|
||||||
uni.showToast({ title: '功能待接入', icon: 'none' })
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoad((opts) => {
|
|
||||||
const id = (opts && opts.id) || ''
|
|
||||||
if (id) fetchDetail(id)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.page { height: 100vh }
|
|
||||||
.banner { padding: 24rpx }
|
|
||||||
.banner-img { width: 100% }
|
|
||||||
.header { padding: 0 24rpx }
|
|
||||||
.title { font-size: 36rpx; font-weight: 700 }
|
|
||||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
|
||||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
|
||||||
.btn { flex: 1 }
|
|
||||||
.primary { background-color: #007AFF; color: #fff }
|
|
||||||
</style>
|
|
||||||
@ -5,11 +5,11 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="header">
|
<view class="header">
|
||||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||||
<view class="meta" v-if="detail.price_draw !== undefined">单次抽选:¥{{ detail.price_draw }}</view>
|
<view class="meta" v-if="detail.price_draw !== undefined">单次抽选:¥{{ (Number(detail.price_draw || 0) / 100).toFixed(2) }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="draw-actions">
|
<view class="draw-actions">
|
||||||
<button class="draw-btn" @click="() => onMachineDraw(1)">单次抽选</button>
|
<button class="draw-btn" @click="() => openPayment(1)">参加一次</button>
|
||||||
<button class="draw-btn" @click="() => onMachineDraw(10)">十次抽选</button>
|
<button class="draw-btn" @click="() => openPayment(10)">参加十次</button>
|
||||||
<button class="draw-btn secondary" @click="onMachineTry">试一试</button>
|
<button class="draw-btn secondary" @click="onMachineTry">试一试</button>
|
||||||
</view>
|
</view>
|
||||||
<view class="issues" v-if="showIssues && issues.length">
|
<view class="issues" v-if="showIssues && issues.length">
|
||||||
@ -27,6 +27,13 @@
|
|||||||
<button class="overlay-close" @tap="closeFlip">关闭</button>
|
<button class="overlay-close" @tap="closeFlip">关闭</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<PaymentPopup
|
||||||
|
v-model:visible="paymentVisible"
|
||||||
|
:amount="paymentAmount"
|
||||||
|
:coupons="coupons"
|
||||||
|
:propCards="propCards"
|
||||||
|
@confirm="onPaymentConfirm"
|
||||||
|
/>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -35,7 +42,8 @@ import { ref, computed, getCurrentInstance } from 'vue'
|
|||||||
import ElCard from '../../../components/ElCard.vue'
|
import ElCard from '../../../components/ElCard.vue'
|
||||||
import FlipGrid from '../../../components/FlipGrid.vue'
|
import FlipGrid from '../../../components/FlipGrid.vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards, drawActivityIssue } from '../../../api/appUser'
|
import PaymentPopup from '../../../components/PaymentPopup.vue'
|
||||||
|
import { getActivityDetail, getActivityIssues, getActivityIssueRewards, joinLottery, createWechatOrder, getLotteryResult, getItemCards, getUserCoupons } from '../../../api/appUser'
|
||||||
|
|
||||||
const detail = ref({})
|
const detail = ref({})
|
||||||
const statusText = ref('')
|
const statusText = ref('')
|
||||||
@ -56,6 +64,14 @@ const currentIssueTitle = computed(() => {
|
|||||||
const points = ref(0)
|
const points = ref(0)
|
||||||
const flipRef = ref(null)
|
const flipRef = ref(null)
|
||||||
const showFlip = ref(false)
|
const showFlip = ref(false)
|
||||||
|
const paymentVisible = ref(false)
|
||||||
|
const paymentAmount = ref('0.00')
|
||||||
|
const coupons = ref([])
|
||||||
|
const propCards = ref([])
|
||||||
|
const pendingCount = ref(1)
|
||||||
|
const selectedCoupon = ref(null)
|
||||||
|
const selectedCard = ref(null)
|
||||||
|
const pricePerDrawYuan = computed(() => ((Number(detail.value.price_draw || 0) / 100) || 0))
|
||||||
|
|
||||||
function statusToText(s) {
|
function statusToText(s) {
|
||||||
if (s === 1) return '进行中'
|
if (s === 1) return '进行中'
|
||||||
@ -220,27 +236,134 @@ function onPreviewBanner() {
|
|||||||
if (url) uni.previewImage({ urls: [url], current: url })
|
if (url) uni.previewImage({ urls: [url], current: url })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openPayment(count) {
|
||||||
|
const times = Math.max(1, Number(count || 1))
|
||||||
|
pendingCount.value = times
|
||||||
|
paymentAmount.value = (pricePerDrawYuan.value * times).toFixed(2)
|
||||||
|
const token = uni.getStorageSync('token')
|
||||||
|
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||||
|
if (!token || !phoneBound) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '请先登录并绑定手机号',
|
||||||
|
confirmText: '去登录',
|
||||||
|
success: (res) => { if (res.confirm) uni.navigateTo({ url: '/pages/login/index' }) }
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
paymentVisible.value = true
|
||||||
|
fetchPropCards()
|
||||||
|
fetchCoupons()
|
||||||
|
}
|
||||||
|
|
||||||
function onMachineDraw(count) {
|
async function onPaymentConfirm(data) {
|
||||||
|
selectedCoupon.value = data && data.coupon ? data.coupon : null
|
||||||
|
selectedCard.value = data && data.card ? data.card : null
|
||||||
|
paymentVisible.value = false
|
||||||
|
await onMachineDraw(pendingCount.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPropCards() {
|
||||||
|
const user_id = uni.getStorageSync('user_id')
|
||||||
|
if (!user_id) return
|
||||||
|
try {
|
||||||
|
const res = await getItemCards(user_id)
|
||||||
|
let list = []
|
||||||
|
if (Array.isArray(res)) list = res
|
||||||
|
else if (res && Array.isArray(res.list)) list = res.list
|
||||||
|
else if (res && Array.isArray(res.data)) list = res.data
|
||||||
|
propCards.value = list.map((i, idx) => ({
|
||||||
|
id: i.id ?? i.card_id ?? String(idx),
|
||||||
|
name: i.name ?? i.title ?? '道具卡'
|
||||||
|
}))
|
||||||
|
} catch (e) {
|
||||||
|
propCards.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCoupons() {
|
||||||
|
const user_id = uni.getStorageSync('user_id')
|
||||||
|
if (!user_id) return
|
||||||
|
try {
|
||||||
|
const res = await getUserCoupons(user_id, 0, 1, 100)
|
||||||
|
let list = []
|
||||||
|
if (Array.isArray(res)) list = res
|
||||||
|
else if (res && Array.isArray(res.list)) list = res.list
|
||||||
|
else if (res && Array.isArray(res.data)) list = res.data
|
||||||
|
coupons.value = list.map((i, idx) => {
|
||||||
|
const amountCents = (i.remaining !== undefined && i.remaining !== null) ? Number(i.remaining) : Number(i.amount ?? i.value ?? 0)
|
||||||
|
const amt = isNaN(amountCents) ? 0 : (amountCents / 100)
|
||||||
|
return {
|
||||||
|
id: i.id ?? i.coupon_id ?? String(idx),
|
||||||
|
name: i.name ?? i.title ?? '优惠券',
|
||||||
|
amount: Number(amt).toFixed(2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
coupons.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onMachineDraw(count) {
|
||||||
showFlip.value = true
|
showFlip.value = true
|
||||||
try { if (flipRef.value && flipRef.value.reset) flipRef.value.reset() } catch (_) {}
|
try { if (flipRef.value && flipRef.value.reset) flipRef.value.reset() } catch (_) {}
|
||||||
const aid = activityId.value || ''
|
const aid = activityId.value || ''
|
||||||
const iid = currentIssueId.value || ''
|
const iid = currentIssueId.value || ''
|
||||||
if (!aid || !iid) { uni.showToast({ title: '期数未选择', icon: 'none' }); return }
|
if (!aid || !iid) { uni.showToast({ title: '期数未选择', icon: 'none' }); return }
|
||||||
|
const token = uni.getStorageSync('token')
|
||||||
|
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||||
|
if (!token || !phoneBound) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '请先登录并绑定手机号',
|
||||||
|
confirmText: '去登录',
|
||||||
|
success: (res) => { if (res.confirm) uni.navigateTo({ url: '/pages/login/index' }) }
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const openid = uni.getStorageSync('openid')
|
||||||
|
if (!openid) { uni.showToast({ title: '缺少OpenID,请重新登录', icon: 'none' }); return }
|
||||||
drawLoading.value = true
|
drawLoading.value = true
|
||||||
const times = Math.max(1, Number(count || 1))
|
try {
|
||||||
const calls = Array(times).fill(0).map(() => drawActivityIssue(aid, iid))
|
const times = Math.max(1, Number(count || 1))
|
||||||
Promise.allSettled(calls).then(list => {
|
const joinRes = await joinLottery({
|
||||||
drawLoading.value = false
|
activity_id: Number(aid),
|
||||||
const items = list.map(r => {
|
issue_id: Number(iid),
|
||||||
const obj = r.status === 'fulfilled' ? r.value : {}
|
channel: 'miniapp',
|
||||||
const data = obj && (obj.data || obj.result || obj.reward || obj.item || obj)
|
count: times,
|
||||||
const title = String((data && (data.title || data.name || data.product_name)) || '未知奖励')
|
coupon_id: selectedCoupon.value && selectedCoupon.value.id ? Number(selectedCoupon.value.id) : 0,
|
||||||
const image = String((data && (data.image || data.img || data.pic || data.product_image)) || '')
|
item_card_id: selectedCard.value && selectedCard.value.id ? Number(selectedCard.value.id) : 0
|
||||||
|
})
|
||||||
|
const orderNo = joinRes && (joinRes.order_no || joinRes.data?.order_no || joinRes.result?.order_no)
|
||||||
|
if (!orderNo) throw new Error('未获取到订单号')
|
||||||
|
const payRes = await createWechatOrder({ openid, order_no: orderNo })
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
uni.requestPayment({
|
||||||
|
provider: 'wxpay',
|
||||||
|
timeStamp: payRes.timeStamp || payRes.timestamp,
|
||||||
|
nonceStr: payRes.nonceStr || payRes.noncestr,
|
||||||
|
package: payRes.package,
|
||||||
|
signType: payRes.signType || 'MD5',
|
||||||
|
paySign: payRes.paySign,
|
||||||
|
success: resolve,
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const resultRes = await getLotteryResult(orderNo)
|
||||||
|
const raw = resultRes && (resultRes.list || resultRes.items || resultRes.data || resultRes.result || resultRes)
|
||||||
|
const arr = Array.isArray(raw) ? raw : (Array.isArray(resultRes?.data) ? resultRes.data : [raw])
|
||||||
|
const items = arr.filter(Boolean).map(d => {
|
||||||
|
const title = String((d && (d.title || d.name || d.product_name)) || '奖励')
|
||||||
|
const image = String((d && (d.image || d.img || d.pic || d.product_image)) || '')
|
||||||
return { title, image }
|
return { title, image }
|
||||||
})
|
})
|
||||||
if (flipRef.value && flipRef.value.revealResults) flipRef.value.revealResults(items)
|
if (flipRef.value && flipRef.value.revealResults) flipRef.value.revealResults(items)
|
||||||
}).catch(() => { drawLoading.value = false; if (flipRef.value && flipRef.value.revealResults) flipRef.value.revealResults([{ title: '抽选失败', image: '' }]) })
|
} catch (e) {
|
||||||
|
if (flipRef.value && flipRef.value.revealResults) flipRef.value.revealResults([{ title: e.message || '抽选失败', image: '' }])
|
||||||
|
uni.showToast({ title: e.message || '操作失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
drawLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMachineTry() {
|
function onMachineTry() {
|
||||||
|
|||||||
@ -1,66 +0,0 @@
|
|||||||
<template>
|
|
||||||
<scroll-view class="page" scroll-y>
|
|
||||||
<view class="banner" v-if="detail.banner">
|
|
||||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
|
||||||
</view>
|
|
||||||
<view class="header">
|
|
||||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
|
||||||
<view class="meta">分类:{{ detail.category_name || '一番赏' }}</view>
|
|
||||||
<view class="meta" v-if="detail.price_draw !== undefined">抽选价:¥{{ detail.price_draw }}</view>
|
|
||||||
<view class="meta" v-if="detail.status !== undefined">状态:{{ statusText }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="actions">
|
|
||||||
<button class="btn" @click="onPreviewBanner">查看图片</button>
|
|
||||||
<button class="btn primary" @click="onParticipate">立即参与</button>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
|
||||||
import { getActivityDetail } from '../../api/appUser'
|
|
||||||
|
|
||||||
const detail = ref({})
|
|
||||||
|
|
||||||
function statusToText(s) {
|
|
||||||
if (s === 1) return '进行中'
|
|
||||||
if (s === 0) return '未开始'
|
|
||||||
if (s === 2) return '已结束'
|
|
||||||
return String(s || '')
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusText = ref('')
|
|
||||||
|
|
||||||
async function fetchDetail(id) {
|
|
||||||
const data = await getActivityDetail(id)
|
|
||||||
detail.value = data || {}
|
|
||||||
statusText.value = statusToText(detail.value.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPreviewBanner() {
|
|
||||||
const url = detail.value.banner || ''
|
|
||||||
if (url) uni.previewImage({ urls: [url], current: url })
|
|
||||||
}
|
|
||||||
|
|
||||||
function onParticipate() {
|
|
||||||
uni.showToast({ title: '功能待接入', icon: 'none' })
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoad((opts) => {
|
|
||||||
const id = (opts && opts.id) || ''
|
|
||||||
if (id) fetchDetail(id)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.page { height: 100vh }
|
|
||||||
.banner { padding: 24rpx }
|
|
||||||
.banner-img { width: 100% }
|
|
||||||
.header { padding: 0 24rpx }
|
|
||||||
.title { font-size: 36rpx; font-weight: 700 }
|
|
||||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
|
||||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
|
||||||
.btn { flex: 1 }
|
|
||||||
.primary { background-color: #007AFF; color: #fff }
|
|
||||||
</style>
|
|
||||||
@ -5,13 +5,9 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="header">
|
<view class="header">
|
||||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||||
<view class="meta" v-if="detail.price_draw !== undefined">抽选价:¥{{ detail.price_draw }}</view>
|
<view class="meta" v-if="detail.price_draw !== undefined">抽选价:¥{{ (Number(detail.price_draw || 0) / 100).toFixed(2) }}</view>
|
||||||
</view>
|
|
||||||
<view class="draw-actions">
|
|
||||||
<button class="draw-btn" @click="() => onMachineDraw(1)">单次抽选</button>
|
|
||||||
<button class="draw-btn" @click="() => onMachineDraw(10)">十次抽选</button>
|
|
||||||
<button class="draw-btn secondary" @click="onMachineTry">试一试</button>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="issues" v-if="showIssues && issues.length">
|
<view class="issues" v-if="showIssues && issues.length">
|
||||||
<view class="issue-switch">
|
<view class="issue-switch">
|
||||||
<button class="switch-btn" @click="prevIssue">〈</button>
|
<button class="switch-btn" @click="prevIssue">〈</button>
|
||||||
@ -19,6 +15,17 @@
|
|||||||
<button class="switch-btn" @click="nextIssue">〉</button>
|
<button class="switch-btn" @click="nextIssue">〉</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 引入位置选择组件 -->
|
||||||
|
<view class="selector-section" v-if="activityId && currentIssueId">
|
||||||
|
<YifanSelector
|
||||||
|
:activity-id="activityId"
|
||||||
|
:issue-id="currentIssueId"
|
||||||
|
:price-per-draw="Number(detail.price_draw || 0) / 100"
|
||||||
|
@payment-success="onPaymentSuccess"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
<view v-if="showFlip" class="flip-overlay" @touchmove.stop.prevent>
|
<view v-if="showFlip" class="flip-overlay" @touchmove.stop.prevent>
|
||||||
<view class="flip-mask" @tap="closeFlip"></view>
|
<view class="flip-mask" @tap="closeFlip"></view>
|
||||||
@ -35,7 +42,8 @@ import { ref, computed, getCurrentInstance } from 'vue'
|
|||||||
import ElCard from '../../../components/ElCard.vue'
|
import ElCard from '../../../components/ElCard.vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import FlipGrid from '../../../components/FlipGrid.vue'
|
import FlipGrid from '../../../components/FlipGrid.vue'
|
||||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards, drawActivityIssue, getActivityWinRecords } from '../../../api/appUser'
|
import YifanSelector from '@/components/YifanSelector.vue'
|
||||||
|
import { getActivityDetail, getActivityIssues, getActivityIssueRewards, getActivityWinRecords } from '../../../api/appUser'
|
||||||
|
|
||||||
const detail = ref({})
|
const detail = ref({})
|
||||||
const issues = ref([])
|
const issues = ref([])
|
||||||
@ -242,34 +250,38 @@ function onPreviewBanner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function onMachineDraw(count) {
|
function onPaymentSuccess(payload) {
|
||||||
|
console.log('Payment Success:', payload)
|
||||||
|
|
||||||
|
const result = payload.result
|
||||||
|
let wonItems = []
|
||||||
|
|
||||||
|
// 尝试解析返回结果中的奖励列表
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
wonItems = result
|
||||||
|
} else if (result && Array.isArray(result.list)) {
|
||||||
|
wonItems = result.list
|
||||||
|
} else if (result && Array.isArray(result.data)) {
|
||||||
|
wonItems = result.data
|
||||||
|
} else if (result && Array.isArray(result.rewards)) {
|
||||||
|
wonItems = result.rewards
|
||||||
|
} else {
|
||||||
|
// 兜底:如果是单对象或无法识别,尝试作为单个物品处理
|
||||||
|
wonItems = result ? [result] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = wonItems.map(data => {
|
||||||
|
const title = String((data && (data.title || data.name || data.product_name || data.reward_name)) || '未知奖励')
|
||||||
|
const image = String((data && (data.image || data.img || data.pic || data.product_image || data.reward_image)) || '')
|
||||||
|
return { title, image }
|
||||||
|
})
|
||||||
|
|
||||||
showFlip.value = true
|
showFlip.value = true
|
||||||
try { if (flipRef.value && flipRef.value.reset) flipRef.value.reset() } catch (_) {}
|
try { if (flipRef.value && flipRef.value.reset) flipRef.value.reset() } catch (_) {}
|
||||||
const aid = activityId.value || ''
|
|
||||||
const iid = currentIssueId.value || ''
|
setTimeout(() => {
|
||||||
if (!aid || !iid) { uni.showToast({ title: '期数未选择', icon: 'none' }); return }
|
|
||||||
drawLoading.value = true
|
|
||||||
const times = Math.max(1, Number(count || 1))
|
|
||||||
const calls = Array(times).fill(0).map(() => drawActivityIssue(aid, iid))
|
|
||||||
Promise.allSettled(calls).then(list => {
|
|
||||||
drawLoading.value = false
|
|
||||||
const items = list.map(r => {
|
|
||||||
const obj = r.status === 'fulfilled' ? r.value : {}
|
|
||||||
const data = obj && (obj.data || obj.result || obj.reward || obj.item || obj)
|
|
||||||
const title = String((data && (data.title || data.name || data.product_name)) || '未知奖励')
|
|
||||||
const image = String((data && (data.image || data.img || data.pic || data.product_image)) || '')
|
|
||||||
return { title, image }
|
|
||||||
})
|
|
||||||
if (flipRef.value && flipRef.value.revealResults) flipRef.value.revealResults(items)
|
if (flipRef.value && flipRef.value.revealResults) flipRef.value.revealResults(items)
|
||||||
}).catch(() => { drawLoading.value = false; if (flipRef.value && flipRef.value.revealResults) flipRef.value.revealResults([{ title: '抽选失败', image: '' }]) })
|
}, 100)
|
||||||
}
|
|
||||||
|
|
||||||
function onMachineTry() {
|
|
||||||
const list = rewardsMap.value[currentIssueId.value] || []
|
|
||||||
if (!list.length) { uni.showToast({ title: '暂无奖池', icon: 'none' }); return }
|
|
||||||
const idx = Math.floor(Math.random() * list.length)
|
|
||||||
const it = list[idx]
|
|
||||||
uni.showModal({ title: '试一试', content: it.title || '随机预览', showCancel: false, success: () => { if (it.image) uni.previewImage({ urls: [it.image], current: it.image }) } })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad((opts) => {
|
onLoad((opts) => {
|
||||||
@ -307,7 +319,19 @@ function closeFlip() { showFlip.value = false }
|
|||||||
.issues-title { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx }
|
.issues-title { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx }
|
||||||
.issues-list { }
|
.issues-list { }
|
||||||
.issue-switch { display: flex; align-items: center; justify-content: center; gap: 12rpx; margin: 0 24rpx 24rpx }
|
.issue-switch { display: flex; align-items: center; justify-content: center; gap: 12rpx; margin: 0 24rpx 24rpx }
|
||||||
.switch-btn { width: 72rpx; height: 72rpx; border-radius: 999rpx; background: #fff3df; border: 2rpx solid #f0c58a; color: #8a5a2b }
|
.switch-btn {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: #fff3df;
|
||||||
|
border: 2rpx solid #f0c58a;
|
||||||
|
color: #8a5a2b;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
.issue-title { font-size: 28rpx; color: #6b4b1f; background: #ffdfaa; border-radius: 12rpx; padding: 8rpx 16rpx }
|
.issue-title { font-size: 28rpx; color: #6b4b1f; background: #ffdfaa; border-radius: 12rpx; padding: 8rpx 16rpx }
|
||||||
.reward { display: flex; align-items: center; margin-bottom: 8rpx }
|
.reward { display: flex; align-items: center; margin-bottom: 8rpx }
|
||||||
.reward-img { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 12rpx; background: #f5f5f5 }
|
.reward-img { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 12rpx; background: #f5f5f5 }
|
||||||
|
|||||||
@ -1,13 +1,123 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="wrap">货柜</view>
|
<view class="wrap">
|
||||||
|
<!-- 顶部 Tab -->
|
||||||
|
<view class="tabs">
|
||||||
|
<view class="tab-item" :class="{ active: currentTab === 0 }" @tap="switchTab(0)">
|
||||||
|
<text class="tab-text">待处理</text>
|
||||||
|
<text class="tab-count" v-if="aggregatedList.length > 0">({{ aggregatedList.length }})</text>
|
||||||
|
</view>
|
||||||
|
<view class="tab-item" :class="{ active: currentTab === 1 }" @tap="switchTab(1)">
|
||||||
|
<text class="tab-text">已申请发货</text>
|
||||||
|
<text class="tab-count" v-if="shippedList.length > 0">({{ shippedList.length }})</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Tab 0: 待处理商品 -->
|
||||||
|
<block v-if="currentTab === 0">
|
||||||
|
<!-- 全选栏 -->
|
||||||
|
<view class="action-bar" v-if="aggregatedList.length > 0">
|
||||||
|
<view class="select-all" @tap="toggleSelectAll">
|
||||||
|
<view class="checkbox" :class="{ checked: isAllSelected }"></view>
|
||||||
|
<text>全选</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="loading && aggregatedList.length === 0" class="status-text">加载中...</view>
|
||||||
|
<view v-else-if="!aggregatedList || aggregatedList.length === 0" class="status-text">背包空空如也</view>
|
||||||
|
|
||||||
|
<view v-else class="inventory-grid">
|
||||||
|
<view v-for="(item, index) in aggregatedList" :key="index" class="inventory-item">
|
||||||
|
<view class="checkbox-area" @tap.stop="toggleSelect(item)">
|
||||||
|
<view class="checkbox" :class="{ checked: item.selected }"></view>
|
||||||
|
</view>
|
||||||
|
<image :src="item.image" mode="aspectFill" class="item-image" @error="onImageError(index, 'aggregated')" />
|
||||||
|
<view class="item-info">
|
||||||
|
<text class="item-name">{{ item.name || '未命名道具' }}</text>
|
||||||
|
<text class="item-price" v-if="item.price">单价: ¥{{ item.price }}</text>
|
||||||
|
<view class="item-actions">
|
||||||
|
<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>
|
||||||
|
<text class="step-num">{{ item.selectedCount }}</text>
|
||||||
|
<text class="step-btn plus" @tap.stop="changeCount(item, 1)">+</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="loading && aggregatedList.length > 0" class="loading-more">加载更多...</view>
|
||||||
|
<view v-if="!hasMore && aggregatedList.length > 0" class="no-more">没有更多了</view>
|
||||||
|
|
||||||
|
<!-- 底部操作栏 -->
|
||||||
|
<view class="bottom-bar" v-if="hasSelected">
|
||||||
|
<view class="selected-info">已选 {{ totalSelectedCount }} 件</view>
|
||||||
|
<view class="btn-group">
|
||||||
|
<button class="action-btn btn-ship" @tap="onShip">发货</button>
|
||||||
|
<button class="action-btn btn-redeem" @tap="onRedeem">兑换</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="bottom-spacer" v-if="hasSelected"></view>
|
||||||
|
</block>
|
||||||
|
|
||||||
|
<!-- Tab 1: 已申请发货 -->
|
||||||
|
<block v-if="currentTab === 1">
|
||||||
|
<view v-if="loading && shippedList.length === 0" class="status-text">加载中...</view>
|
||||||
|
<view v-else-if="!shippedList || shippedList.length === 0" class="status-text">暂无已发货记录</view>
|
||||||
|
|
||||||
|
<view v-else class="inventory-grid">
|
||||||
|
<view v-for="(item, index) in shippedList" :key="index" class="inventory-item">
|
||||||
|
<!-- 已发货仅展示 -->
|
||||||
|
<image :src="item.image" mode="aspectFill" class="item-image" @error="onImageError(index, 'shipped')" />
|
||||||
|
<view class="item-info">
|
||||||
|
<text class="item-name">{{ item.name || '未命名道具' }}</text>
|
||||||
|
<text class="item-count">x{{ item.count || 1 }}</text>
|
||||||
|
<text class="item-status">已申请发货</text>
|
||||||
|
<text class="item-meta" v-if="item.express_code || item.express_no">快递:{{ item.express_code }} {{ item.express_no }}</text>
|
||||||
|
<text class="item-meta" v-if="item.shipped_at">发货时间:{{ formatDate(item.shipped_at) }}</text>
|
||||||
|
<text class="item-meta" v-if="item.received_at">签收时间:{{ formatDate(item.received_at) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="loading && shippedList.length > 0" class="loading-more">加载更多...</view>
|
||||||
|
<view v-if="!hasMore && shippedList.length > 0" class="no-more">没有更多了</view>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onShow } from '@dcloudio/uni-app'
|
import { ref, computed } from 'vue'
|
||||||
|
import { onShow, onReachBottom } from '@dcloudio/uni-app'
|
||||||
|
import { getInventory, getProductDetail, redeemInventory, requestShipping, listAddresses, getShipments } from '@/api/appUser'
|
||||||
|
|
||||||
|
const currentTab = ref(0)
|
||||||
|
const aggregatedList = ref([])
|
||||||
|
const shippedList = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const page = ref(1)
|
||||||
|
const pageSize = ref(100)
|
||||||
|
const hasMore = ref(true)
|
||||||
|
|
||||||
|
const totalCount = computed(() => {
|
||||||
|
return aggregatedList.value.reduce((sum, item) => sum + (item.count || 1), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasSelected = computed(() => {
|
||||||
|
return aggregatedList.value.some(item => item.selected)
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalSelectedCount = computed(() => {
|
||||||
|
return aggregatedList.value.reduce((sum, item) => sum + (item.selected ? item.selectedCount : 0), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isAllSelected = computed(() => {
|
||||||
|
return aggregatedList.value.length > 0 && aggregatedList.value.every(item => item.selected)
|
||||||
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
const token = uni.getStorageSync('token')
|
const token = uni.getStorageSync('token')
|
||||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||||
console.log('cabinet onShow token:', token, 'isLogin:', !!token, 'phoneBound:', phoneBound)
|
console.log('cabinet onShow token:', token, 'isLogin:', !!token, 'phoneBound:', phoneBound)
|
||||||
|
|
||||||
if (!token || !phoneBound) {
|
if (!token || !phoneBound) {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
@ -19,10 +129,686 @@ onShow(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置并加载第一页
|
||||||
|
page.value = 1
|
||||||
|
hasMore.value = true
|
||||||
|
aggregatedList.value = []
|
||||||
|
shippedList.value = []
|
||||||
|
const uid = uni.getStorageSync("user_id")
|
||||||
|
if (currentTab.value === 1) {
|
||||||
|
loadShipments(uid)
|
||||||
|
} else {
|
||||||
|
loadAllInventory(uid)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onReachBottom(() => {
|
||||||
|
if (hasMore.value && !loading.value) {
|
||||||
|
const uid = uni.getStorageSync("user_id")
|
||||||
|
if (currentTab.value === 1) {
|
||||||
|
loadShipments(uid)
|
||||||
|
} else {
|
||||||
|
loadInventory(uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function switchTab(index) {
|
||||||
|
currentTab.value = index
|
||||||
|
// 切换时重新加载数据
|
||||||
|
page.value = 1
|
||||||
|
hasMore.value = true
|
||||||
|
aggregatedList.value = []
|
||||||
|
shippedList.value = []
|
||||||
|
const uid = uni.getStorageSync("user_id")
|
||||||
|
if (currentTab.value === 1) {
|
||||||
|
loadShipments(uid)
|
||||||
|
} else {
|
||||||
|
loadAllInventory(uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanUrl(u) {
|
||||||
|
if (!u) return '/static/logo.png'
|
||||||
|
let s = String(u).trim()
|
||||||
|
|
||||||
|
// 尝试解析 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 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'
|
||||||
|
}
|
||||||
|
|
||||||
|
function onImageError(index, type = 'aggregated') {
|
||||||
|
if (type === 'aggregated' && aggregatedList.value[index]) {
|
||||||
|
aggregatedList.value[index].image = '/static/logo.png'
|
||||||
|
} else if (type === 'shipped' && shippedList.value[index]) {
|
||||||
|
shippedList.value[index].image = '/static/logo.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateStr) {
|
||||||
|
if (!dateStr) return ''
|
||||||
|
const d = new Date(dateStr)
|
||||||
|
if (isNaN(d.getTime())) return ''
|
||||||
|
const y = d.getFullYear()
|
||||||
|
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||||
|
const da = String(d.getDate()).padStart(2, '0')
|
||||||
|
const h = String(d.getHours()).padStart(2, '0')
|
||||||
|
const min = String(d.getMinutes()).padStart(2, '0')
|
||||||
|
return `${y}-${m}-${da} ${h}:${min}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadShipments(uid) {
|
||||||
|
if (loading.value) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getShipments(uid, page.value, pageSize.value)
|
||||||
|
let list = []
|
||||||
|
let total = 0
|
||||||
|
if (res && Array.isArray(res.list)) { list = res.list; total = res.total || 0 }
|
||||||
|
else if (res && Array.isArray(res.data)) { list = res.data; total = res.total || 0 }
|
||||||
|
else if (Array.isArray(res)) { list = res; total = res.length }
|
||||||
|
const mapped = list.map(s => ({
|
||||||
|
image: '/static/logo.png',
|
||||||
|
name: '发货单',
|
||||||
|
count: s.count ?? (Array.isArray(s.inventory_ids) ? s.inventory_ids.length : 0),
|
||||||
|
express_code: s.express_code || '',
|
||||||
|
express_no: s.express_no || '',
|
||||||
|
shipped_at: s.shipped_at || '',
|
||||||
|
received_at: s.received_at || '',
|
||||||
|
status: s.status
|
||||||
|
}))
|
||||||
|
const next = page.value === 1 ? mapped : [...shippedList.value, ...mapped]
|
||||||
|
shippedList.value = next
|
||||||
|
if (list.length < pageSize.value || (page.value * pageSize.value >= total && total > 0)) { hasMore.value = false } else { page.value += 1 }
|
||||||
|
if (list.length === 0) { hasMore.value = false }
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadInventory(uid) {
|
||||||
|
if (loading.value) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getInventory(uid, page.value, pageSize.value)
|
||||||
|
console.log('Inventory loaded:', res)
|
||||||
|
|
||||||
|
let list = []
|
||||||
|
let total = 0
|
||||||
|
|
||||||
|
if (res && Array.isArray(res.list)) {
|
||||||
|
list = res.list
|
||||||
|
total = res.total || 0
|
||||||
|
} else if (res && Array.isArray(res.data)) { // 兼容 data: [] 格式
|
||||||
|
list = res.data
|
||||||
|
total = res.total || 0
|
||||||
|
} else if (Array.isArray(res)) { // 兼容直接返回数组
|
||||||
|
list = res
|
||||||
|
total = res.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤 status=1 (正常) 或 status=3 (已使用/已发货/已兑换) 的物品
|
||||||
|
// status=1: 正常在背包
|
||||||
|
// status=3: 已处理(可能是已发货或已兑换积分)
|
||||||
|
const filteredList = list.filter(item => {
|
||||||
|
const s = Number(item.status)
|
||||||
|
return s === 1 || s === 3
|
||||||
|
})
|
||||||
|
|
||||||
|
// 调试日志:打印第一条数据以确认字段结构
|
||||||
|
if (filteredList.length > 0) {
|
||||||
|
console.log('Debug Inventory Item:', filteredList[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据当前 Tab 过滤是否发货
|
||||||
|
const targetItems = filteredList.filter(item => {
|
||||||
|
// Tab 0: 待处理 (has_shipment 为 false 且 status=1)
|
||||||
|
// Tab 1: 已申请发货 (has_shipment 为 true)
|
||||||
|
|
||||||
|
// 注意:API 返回的 has_shipment 可能是布尔值 true/false,也可能是数字 1/0,或者是字符串 "true"/"false"
|
||||||
|
// 这里做一个宽容的判断
|
||||||
|
const isShipped = item.has_shipment === true || item.has_shipment === 1 || String(item.has_shipment) === 'true' || String(item.has_shipment) === '1'
|
||||||
|
|
||||||
|
if (currentTab.value === 1) {
|
||||||
|
// 已申请发货列表:必须是已发货状态
|
||||||
|
// 注意:有些记录 status=3 且 has_shipment=true 表示已发货
|
||||||
|
return isShipped
|
||||||
|
} else {
|
||||||
|
// 待处理列表:未发货且 status=1 (status=3 且未发货的可能是已兑换积分,不应显示在待处理)
|
||||||
|
return !isShipped && Number(item.status) === 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Filtered list (status=1, tab=' + currentTab.value + '):', targetItems)
|
||||||
|
|
||||||
|
// 处理新数据
|
||||||
|
const newItems = targetItems.map(item => {
|
||||||
|
let imageUrl = ''
|
||||||
|
try {
|
||||||
|
let rawImg = item.product_images || item.image
|
||||||
|
if (rawImg && typeof rawImg === 'string') {
|
||||||
|
imageUrl = cleanUrl(rawImg)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Image parse error:', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isShipped = item.has_shipment === true || item.has_shipment === 1 || String(item.has_shipment) === 'true' || String(item.has_shipment) === '1'
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: item.product_id || item.id, // 优先使用 product_id
|
||||||
|
original_ids: [item.id], // 初始化 id 数组
|
||||||
|
name: (item.product_name || item.name || '').trim(),
|
||||||
|
image: imageUrl,
|
||||||
|
count: 1,
|
||||||
|
selected: false,
|
||||||
|
selectedCount: 1,
|
||||||
|
has_shipment: isShipped,
|
||||||
|
updated_at: item.updated_at // 保留更新时间用于分组
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Mapped new items:', newItems.length)
|
||||||
|
|
||||||
|
// 正确的聚合逻辑:
|
||||||
|
// 1. 如果是第一页,直接基于 newItems 生成初始列表(带去重聚合)
|
||||||
|
// 2. 如果是后续页,将 newItems 聚合到现有列表中
|
||||||
|
|
||||||
|
// 深拷贝当前列表
|
||||||
|
let currentList = currentTab.value === 1 ? shippedList : aggregatedList
|
||||||
|
let next = page.value === 1 ? [] : [...currentList.value]
|
||||||
|
|
||||||
|
if (currentTab.value === 1) {
|
||||||
|
// 已发货列表:按 updated_at 分组展示
|
||||||
|
// 这里我们实际上不按 ID 聚合,而是直接把新数据追加进去,
|
||||||
|
// 但为了 UI 展示,我们可以在前端通过 computed 或在这里预处理进行分组
|
||||||
|
// 为了保持与原有列表结构一致(flat list),我们这里暂时按照 updated_at + product_id 聚合
|
||||||
|
// 或者:既然用户要求按 updated_at 分组,可能希望看到的是“一次发货申请”作为一个卡片?
|
||||||
|
// 这里的实现逻辑是:如果 updated_at 和 product_id 都相同,则聚合数量;否则作为新条目
|
||||||
|
|
||||||
|
newItems.forEach(newItem => {
|
||||||
|
// 查找是否存在 updated_at 和 product_id 都相同的条目
|
||||||
|
// 注意:updated_at 可能是 ISO 字符串,比较前最好截取到秒或直接比较字符串
|
||||||
|
const existingItem = next.find(i =>
|
||||||
|
i.id == newItem.id &&
|
||||||
|
new Date(i.updated_at).getTime() === new Date(newItem.updated_at).getTime()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (existingItem) {
|
||||||
|
existingItem.count += 1
|
||||||
|
if (Array.isArray(existingItem.original_ids)) {
|
||||||
|
existingItem.original_ids.push(...newItem.original_ids)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next.push(newItem)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 待处理列表:按 product_id (id) 聚合
|
||||||
|
newItems.forEach(newItem => {
|
||||||
|
if (!newItem.id) {
|
||||||
|
next.push(newItem)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingItem = next.find(i => i.id == newItem.id)
|
||||||
|
if (existingItem) {
|
||||||
|
existingItem.count += 1
|
||||||
|
if (Array.isArray(existingItem.original_ids)) {
|
||||||
|
existingItem.original_ids.push(...newItem.original_ids)
|
||||||
|
} else {
|
||||||
|
existingItem.original_ids = [...newItem.original_ids]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next.push(newItem)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Final aggregated list:', JSON.parse(JSON.stringify(next)))
|
||||||
|
|
||||||
|
if (currentTab.value === 1) {
|
||||||
|
shippedList.value = next
|
||||||
|
} else {
|
||||||
|
aggregatedList.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否还有更多
|
||||||
|
// 注意:这里的 total 是总记录数(未过滤 status=1 之前的),
|
||||||
|
// 我们的分页是基于原始数据的,所以判断依据是原始数据的分页进度
|
||||||
|
if (list.length < pageSize.value || (page.value * pageSize.value >= total && total > 0)) {
|
||||||
|
hasMore.value = false
|
||||||
|
} else {
|
||||||
|
page.value += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果返回空列表(且 total 为 0),也认为没有更多了
|
||||||
|
if (list.length === 0) {
|
||||||
|
hasMore.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load inventory:', error)
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadAllInventory(uid) {
|
||||||
|
try {
|
||||||
|
while (hasMore.value) {
|
||||||
|
await loadInventory(uid)
|
||||||
|
}
|
||||||
|
fetchProductPrices()
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchProductPrices() {
|
||||||
|
const currentList = currentTab.value === 1 ? shippedList : aggregatedList
|
||||||
|
const list = currentList.value
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const item = list[i]
|
||||||
|
if (item.id && !item.price) {
|
||||||
|
try {
|
||||||
|
const res = await getProductDetail(item.id)
|
||||||
|
if (res && (res.price !== undefined || res.data?.price !== undefined)) {
|
||||||
|
// 优先取 res.price,其次 res.data.price (兼容不同返回结构)
|
||||||
|
const raw = res.price !== undefined ? res.price : res.data?.price
|
||||||
|
const num = Number(raw)
|
||||||
|
item.price = isNaN(num) ? null : (num / 100)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Fetch price failed for:', item.id, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelect(item) {
|
||||||
|
item.selected = !item.selected
|
||||||
|
if (item.selected) {
|
||||||
|
// 选中时默认数量为最大值
|
||||||
|
item.selectedCount = item.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelectAll() {
|
||||||
|
const newState = !isAllSelected.value
|
||||||
|
aggregatedList.value.forEach(item => {
|
||||||
|
item.selected = newState
|
||||||
|
if (newState) {
|
||||||
|
item.selectedCount = item.count
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeCount(item, delta) {
|
||||||
|
if (!item.selected) return
|
||||||
|
const newCount = item.selectedCount + delta
|
||||||
|
if (newCount >= 1 && newCount <= item.count) {
|
||||||
|
item.selectedCount = newCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onRedeem() {
|
||||||
|
const user_id = uni.getStorageSync('user_id')
|
||||||
|
if (!user_id) return
|
||||||
|
|
||||||
|
const selectedItems = aggregatedList.value.filter(item => item.selected)
|
||||||
|
if (selectedItems.length === 0) return
|
||||||
|
|
||||||
|
// 收集所有需要兑换的 inventory id
|
||||||
|
let allIds = []
|
||||||
|
selectedItems.forEach(item => {
|
||||||
|
// 确保有足够的 original_ids
|
||||||
|
if (item.original_ids && item.original_ids.length >= item.selectedCount) {
|
||||||
|
// 取出前 selectedCount 个 id
|
||||||
|
const idsToRedeem = item.original_ids.slice(0, item.selectedCount)
|
||||||
|
allIds.push(...idsToRedeem)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (allIds.length === 0) {
|
||||||
|
uni.showToast({ title: '选择无效', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: '确认兑换',
|
||||||
|
content: `确定要兑换选中的 ${allIds.length} 件物品吗?此操作不可撤销。`,
|
||||||
|
success: async (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.showLoading({ title: '处理中...' })
|
||||||
|
try {
|
||||||
|
await redeemInventory(user_id, allIds)
|
||||||
|
uni.showToast({ title: '兑换成功', icon: 'success' })
|
||||||
|
// 刷新列表
|
||||||
|
aggregatedList.value = []
|
||||||
|
page.value = 1
|
||||||
|
hasMore.value = true
|
||||||
|
loadAllInventory(user_id)
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: e.message || '兑换失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onShip() {
|
||||||
|
const user_id = uni.getStorageSync('user_id')
|
||||||
|
if (!user_id) return
|
||||||
|
|
||||||
|
const selectedItems = aggregatedList.value.filter(item => item.selected)
|
||||||
|
if (selectedItems.length === 0) return
|
||||||
|
|
||||||
|
// 收集所有需要发货的 inventory id
|
||||||
|
let allIds = []
|
||||||
|
selectedItems.forEach(item => {
|
||||||
|
if (item.original_ids && item.original_ids.length >= item.selectedCount) {
|
||||||
|
const idsToShip = item.original_ids.slice(0, item.selectedCount)
|
||||||
|
allIds.push(...idsToShip)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (allIds.length === 0) {
|
||||||
|
uni.showToast({ title: '选择无效', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 确认发货
|
||||||
|
uni.showModal({
|
||||||
|
title: '确认发货',
|
||||||
|
content: `共 ${allIds.length} 件物品,确认申请发货?`,
|
||||||
|
confirmText: '确认发货',
|
||||||
|
success: async (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.showLoading({ title: '提交中...' })
|
||||||
|
try {
|
||||||
|
await requestShipping(user_id, allIds)
|
||||||
|
uni.showToast({ title: '申请成功', icon: 'success' })
|
||||||
|
// 刷新列表
|
||||||
|
aggregatedList.value = []
|
||||||
|
page.value = 1
|
||||||
|
hasMore.value = true
|
||||||
|
loadAllInventory(user_id)
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: e.message || '申请失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.wrap { padding: 40rpx }
|
.item-status {
|
||||||
</style>
|
font-size: 24rpx;
|
||||||
|
color: #007AFF;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
.item-meta { font-size: 22rpx; color: #666; margin-top: 4rpx }
|
||||||
|
|
||||||
|
.wrap { padding: 30rpx; }
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 8rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
padding: 0 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all .checkbox {
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active {
|
||||||
|
background: #fff;
|
||||||
|
color: #007AFF;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-count {
|
||||||
|
font-size: 24rpx;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header { font-size: 32rpx; font-weight: bold; margin-bottom: 30rpx; }
|
||||||
|
.status-text { text-align: center; color: #999; margin-top: 100rpx; }
|
||||||
|
|
||||||
|
.inventory-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-image {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-count {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-price {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-area {
|
||||||
|
padding: 10rpx 20rpx 10rpx 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border: 2rpx solid #ccc;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox.checked {
|
||||||
|
background-color: #007AFF;
|
||||||
|
border-color: #007AFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox.checked::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -60%) rotate(45deg);
|
||||||
|
width: 10rpx;
|
||||||
|
height: 20rpx;
|
||||||
|
border-right: 4rpx solid #fff;
|
||||||
|
border-bottom: 4rpx solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-actions {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-btn {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-btn.minus { border-right: 1px solid #ddd; }
|
||||||
|
.step-btn.plus { border-left: 1px solid #ddd; }
|
||||||
|
|
||||||
|
.step-num {
|
||||||
|
width: 60rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 30rpx;
|
||||||
|
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);
|
||||||
|
z-index: 100;
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-info {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
margin: 0;
|
||||||
|
height: 64rpx;
|
||||||
|
line-height: 64rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ship {
|
||||||
|
background-color: #f0ad4e;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-redeem {
|
||||||
|
background-color: #dd524d;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-spacer {
|
||||||
|
height: 120rpx;
|
||||||
|
height: calc(120rpx + constant(safe-area-inset-bottom));
|
||||||
|
height: calc(120rpx + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -173,7 +173,7 @@ export default {
|
|||||||
const base = i.subTitle ?? i.sub_title ?? i.subtitle ?? i.desc ?? i.description ?? ''
|
const base = i.subTitle ?? i.sub_title ?? i.subtitle ?? i.desc ?? i.description ?? ''
|
||||||
if (base) return base
|
if (base) return base
|
||||||
const cat = i.category_name ?? i.categoryName ?? ''
|
const cat = i.category_name ?? i.categoryName ?? ''
|
||||||
const price = (i.price_draw !== undefined && i.price_draw !== null) ? `¥${i.price_draw}` : ''
|
const price = (i.price_draw !== undefined && i.price_draw !== null) ? `¥${(Number(i.price_draw || 0) / 100).toFixed(2)}` : ''
|
||||||
const parts = [cat, price].filter(Boolean)
|
const parts = [cat, price].filter(Boolean)
|
||||||
return parts.join(' · ')
|
return parts.join(' · ')
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="container">
|
<view class="container">
|
||||||
<image class="logo" src="/static/logo.png" mode="widthFix"></image>
|
<image class="logo" src="/static/logo.png" mode="widthFix"></image>
|
||||||
<view class="title">微信登录</view>
|
<!-- #ifdef MP-TOUTIAO -->
|
||||||
|
<view class="login-form">
|
||||||
|
<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="pwd"
|
||||||
|
class="input-field"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 记住账号密码 -->
|
||||||
|
<view class="remember-row">
|
||||||
|
<checkbox :value="remember" @change="handleRememberChange" />
|
||||||
|
<text class="remember-text">记住账号密码</text>
|
||||||
|
</view>
|
||||||
|
<!-- 按钮区域 -->
|
||||||
|
<view class="button-group">
|
||||||
|
<button class="btn login-btn" @click="handleLogin">登录</button>
|
||||||
|
</view>
|
||||||
|
<!-- 注册链接 -->
|
||||||
|
<view class="register-link">
|
||||||
|
<text class="register-text" @click="goToRegister">还没有账号?点击注册</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<view class="title">微信登录</view>
|
||||||
|
|
||||||
|
|
||||||
<button class="btn" open-type="getPhoneNumber" :disabled="loading" @getphonenumber="onGetPhoneNumber">授权手机号快速登录</button>
|
<button class="btn" open-type="getPhoneNumber" :disabled="loading" @getphonenumber="onGetPhoneNumber">授权手机号快速登录</button>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<view class="agreements">
|
<view class="agreements">
|
||||||
@ -18,15 +59,39 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed,onMounted } from 'vue'
|
||||||
import { wechatLogin, bindPhone, getUserStats, getPointsBalance } from '../../api/appUser'
|
import { wechatLogin, bindPhone, getUserStats, getPointsBalance } from '../../api/appUser'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
const needBindPhone = ref(false)
|
const needBindPhone = ref(false)
|
||||||
|
const account =ref("")
|
||||||
|
const pwd = ref ("")
|
||||||
|
const remember=ref(false)
|
||||||
const loggedIn = computed(() => !!uni.getStorageSync('token'))
|
const loggedIn = computed(() => !!uni.getStorageSync('token'))
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
try {
|
||||||
|
const saved = uni.getStorageSync('loginInfo')
|
||||||
|
if (saved && saved.account && saved.pwd) {
|
||||||
|
account.value = saved.account
|
||||||
|
pwd.value = saved.pwd
|
||||||
|
remember.value = true
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('读取本地登录信息失败', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 切换“记住密码”
|
||||||
|
const handleRememberChange = (e) => {
|
||||||
|
remember.value = e.detail.value.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function goToRegister() { uni.navigateTo({ url: '/pages/register/register' }) }
|
||||||
function onLogin() {}
|
function onLogin() {}
|
||||||
|
|
||||||
function toUserAgreement() { uni.navigateTo({ url: '/pages/agreement/user' }) }
|
function toUserAgreement() { uni.navigateTo({ url: '/pages/agreement/user' }) }
|
||||||
@ -48,13 +113,16 @@ function onGetPhoneNumber(e) {
|
|||||||
try {
|
try {
|
||||||
const loginCode = res.code
|
const loginCode = res.code
|
||||||
console.log('login_flow uni.login success, loginCode exists:', !!loginCode)
|
console.log('login_flow uni.login success, loginCode exists:', !!loginCode)
|
||||||
const data = await wechatLogin(loginCode)
|
const inviterCode = uni.getStorageSync('inviter_code')
|
||||||
|
console.log('login_flow using inviter_code:', inviterCode)
|
||||||
|
const data = await wechatLogin(loginCode, inviterCode)
|
||||||
console.log('login_flow wechatLogin response user_id:', data && data.user_id)
|
console.log('login_flow wechatLogin response user_id:', data && data.user_id)
|
||||||
const token = data && data.token
|
const token = data && data.token
|
||||||
const user_id = data && data.user_id
|
const user_id = data && data.user_id
|
||||||
const avatar = data && data.avatar
|
const avatar = data && data.avatar
|
||||||
const nickname = data && data.nickname
|
const nickname = data && data.nickname
|
||||||
const invite_code = data && data.invite_code
|
const invite_code = data && data.invite_code
|
||||||
|
const openid = data && data.openid
|
||||||
uni.setStorageSync('user_info', data || {})
|
uni.setStorageSync('user_info', data || {})
|
||||||
if (token) {
|
if (token) {
|
||||||
uni.setStorageSync('token', token)
|
uni.setStorageSync('token', token)
|
||||||
@ -73,6 +141,9 @@ function onGetPhoneNumber(e) {
|
|||||||
if (invite_code) {
|
if (invite_code) {
|
||||||
uni.setStorageSync('invite_code', invite_code)
|
uni.setStorageSync('invite_code', invite_code)
|
||||||
}
|
}
|
||||||
|
if (openid) {
|
||||||
|
uni.setStorageSync('openid', openid)
|
||||||
|
}
|
||||||
console.log('login_flow bindPhone start')
|
console.log('login_flow bindPhone start')
|
||||||
try {
|
try {
|
||||||
// 首次绑定前短暂延迟,确保服务端 token 生效
|
// 首次绑定前短暂延迟,确保服务端 token 生效
|
||||||
@ -87,7 +158,7 @@ function onGetPhoneNumber(e) {
|
|||||||
const relogin = await new Promise((resolve, reject) => {
|
const relogin = await new Promise((resolve, reject) => {
|
||||||
uni.login({ provider: 'weixin', success: resolve, fail: reject })
|
uni.login({ provider: 'weixin', success: resolve, fail: reject })
|
||||||
})
|
})
|
||||||
const data2 = await wechatLogin(relogin.code)
|
const data2 = await wechatLogin(relogin.code, inviterCode)
|
||||||
const token2 = data2 && data2.token
|
const token2 = data2 && data2.token
|
||||||
const user2 = data2 && data2.user_id
|
const user2 = data2 && data2.user_id
|
||||||
if (token2) uni.setStorageSync('token', token2)
|
if (token2) uni.setStorageSync('token', token2)
|
||||||
@ -134,6 +205,56 @@ function onGetPhoneNumber(e) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.login-form {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.remember-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remember-text {
|
||||||
|
margin-left: 16rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.register-link {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999;
|
||||||
|
text-decoration: underline; /* 可选:加下划线 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-text:active {
|
||||||
|
color: #ff6700; /* 点击时变色 */
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
width: 60px; /* 固定宽度,保证对齐 */
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
border: 2px solid #ccc; /* 边框加粗 */
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
/* 去除 iOS 默认样式 */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
.container { padding: 40rpx; display: flex; flex-direction: column; align-items: center }
|
.container { padding: 40rpx; display: flex; flex-direction: column; align-items: center }
|
||||||
.logo { width: 200rpx; margin-top: 100rpx; margin-bottom: 40rpx }
|
.logo { width: 200rpx; margin-top: 100rpx; margin-bottom: 40rpx }
|
||||||
.title { font-size: 36rpx; margin-bottom: 20rpx }
|
.title { font-size: 36rpx; margin-bottom: 20rpx }
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
<view class="nickname">{{ nickname || '未登录' }}</view>
|
<view class="nickname">{{ nickname || '未登录' }}</view>
|
||||||
<view class="userid">ID:{{ userId || '-' }}</view>
|
<view class="userid">ID:{{ userId || '-' }}</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="refresh-btn" @click="refresh" :class="{ 'loading': loading }">
|
||||||
|
<text class="refresh-icon">↻</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="info">
|
<view class="info">
|
||||||
<view class="info-item">
|
<view class="info-item">
|
||||||
@ -14,24 +17,26 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="info-item">
|
<view class="info-item">
|
||||||
<text class="info-label">邀请码</text>
|
<text class="info-label">邀请码</text>
|
||||||
<text class="info-value">{{ inviteCode || '-' }}</text>
|
<view class="invite-value-wrapper">
|
||||||
|
<text class="info-value">{{ inviteCode || '-' }}</text>
|
||||||
|
<button class="share-btn" open-type="share" v-if="inviteCode">分享</button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="stats">
|
<view class="stats">
|
||||||
<view class="stat">
|
<view class="stat" @click="showPointsPopup">
|
||||||
<view class="stat-label" @click="toPoints">积分</view>
|
<view class="stat-label">积分</view>
|
||||||
<view class="stat-value">{{ pointsBalance }}</view>
|
<view class="stat-value">{{ pointsBalance }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat">
|
<view class="stat" @click="showCouponsPopup">
|
||||||
<view class="stat-label">卡券</view>
|
<view class="stat-label">优惠券</view>
|
||||||
<view class="stat-value">{{ stats.coupon_count || 0 }}</view>
|
<view class="stat-value">{{ stats.coupon_count || 0 }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat">
|
<view class="stat" @click="showItemCardsPopup">
|
||||||
<view class="stat-label">道具卡</view>
|
<view class="stat-label">道具卡</view>
|
||||||
<view class="stat-value">{{ stats.item_card_count || 0 }}</view>
|
<view class="stat-value">{{ stats.item_card_count || 0 }}</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<button class="refresh" @click="refresh" :disabled="loading">刷新数据</button>
|
|
||||||
<view v-if="error" class="error">{{ error }}</view>
|
<view v-if="error" class="error">{{ error }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="orders">
|
<view class="orders">
|
||||||
@ -59,12 +64,160 @@
|
|||||||
<text class="addresses-arrow">›</text>
|
<text class="addresses-arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="addresses">
|
||||||
|
<view class="addresses-title">任务中心</view>
|
||||||
|
<view class="addresses-entry" @click="showTasksPopup">
|
||||||
|
<text class="addresses-text">查看我的任务</text>
|
||||||
|
<text class="addresses-arrow">›</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 积分明细弹窗 -->
|
||||||
|
<view class="popup-mask" v-if="pointsVisible" @tap="closePointsPopup">
|
||||||
|
<view class="popup-content" @tap.stop>
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">积分明细</text>
|
||||||
|
<text class="close-btn" @tap="closePointsPopup">×</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<scroll-view scroll-y class="points-list" @scrolltolower="loadMorePoints">
|
||||||
|
<view v-if="pointsLoading && pointsList.length === 0" class="status-text">加载中...</view>
|
||||||
|
<view v-else-if="!pointsList || pointsList.length === 0" class="status-text">暂无积分记录</view>
|
||||||
|
|
||||||
|
<view v-for="(item, index) in pointsList" :key="index" class="point-item">
|
||||||
|
<view class="point-left">
|
||||||
|
<text class="point-desc">{{ getActionText(item.action) }}</text>
|
||||||
|
<text class="point-time">{{ formatDate(item.created_at) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="point-right">
|
||||||
|
<text class="point-amount" :class="{ 'positive': Number(item.points) > 0, 'negative': Number(item.points) < 0 }">
|
||||||
|
{{ Number(item.points) > 0 ? '+' : '' }}{{ item.points }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="pointsLoading && pointsList.length > 0" class="loading-more">加载更多...</view>
|
||||||
|
<view v-if="!pointsHasMore && pointsList.length > 0" class="no-more">没有更多了</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 优惠券弹窗 -->
|
||||||
|
<view class="popup-mask" v-if="couponsVisible" @tap="closeCouponsPopup">
|
||||||
|
<view class="popup-content" @tap.stop>
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">我的优惠券</text>
|
||||||
|
<text class="close-btn" @tap="closeCouponsPopup">×</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-tabs">
|
||||||
|
<view class="popup-tab" :class="{ active: couponsTab === 0 }" @tap="switchCouponsTab(0)">未使用</view>
|
||||||
|
<view class="popup-tab" :class="{ active: couponsTab === 1 }" @tap="switchCouponsTab(1)">已使用</view>
|
||||||
|
<view class="popup-tab" :class="{ active: couponsTab === 2 }" @tap="switchCouponsTab(2)">去兑换</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 兑换界面 -->
|
||||||
|
<view v-if="couponsTab === 2" class="redeem-container">
|
||||||
|
<input class="redeem-input" v-model="redeemCode" placeholder="请输入兑换码" />
|
||||||
|
<button class="redeem-btn" @tap="handleRedeem">立即兑换</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 列表界面 -->
|
||||||
|
<scroll-view v-else scroll-y class="points-list" @scrolltolower="loadMoreCoupons">
|
||||||
|
<view v-if="couponsLoading && couponsList.length === 0" class="status-text">加载中...</view>
|
||||||
|
<view v-else-if="!couponsList || couponsList.length === 0" class="status-text">暂无优惠券</view>
|
||||||
|
|
||||||
|
<view v-for="(item, index) in couponsList" :key="index" class="coupon-item">
|
||||||
|
<view class="coupon-left">
|
||||||
|
<text class="coupon-name">{{ item.name || '优惠券' }}</text>
|
||||||
|
<text class="coupon-desc">{{ item.rules || item.description || '' }}</text>
|
||||||
|
<text class="coupon-time">有效期至:{{ formatDate(item.valid_end || item.end_time) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="coupon-right">
|
||||||
|
<view v-if="couponsTab === 0 && (item.remaining !== undefined && item.remaining !== null)" class="coupon-amount-wrapper">
|
||||||
|
<text class="symbol">¥</text>
|
||||||
|
<text class="amount-value">{{ (Number(item.remaining || 0) / 100).toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="coupon-status">{{ couponsTab === 0 ? '未使用' : '已使用/过期' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="couponsLoading && couponsList.length > 0" class="loading-more">加载更多...</view>
|
||||||
|
<view v-if="!couponsHasMore && couponsList.length > 0" class="no-more">没有更多了</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 道具卡弹窗 -->
|
||||||
|
<view class="popup-mask" v-if="itemCardsVisible" @tap="closeItemCardsPopup">
|
||||||
|
<view class="popup-content" @tap.stop>
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">我的道具卡</text>
|
||||||
|
<text class="close-btn" @tap="closeItemCardsPopup">×</text>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="points-list">
|
||||||
|
<view v-if="itemCardsLoading && itemCardsList.length === 0" class="status-text">加载中...</view>
|
||||||
|
<view v-else-if="!itemCardsList || itemCardsList.length === 0" class="status-text">暂无道具卡</view>
|
||||||
|
<view v-for="(item, index) in itemCardsList" :key="index" class="coupon-item">
|
||||||
|
<view class="coupon-left">
|
||||||
|
<text class="coupon-name">{{ item.name || item.title || '道具卡' }}</text>
|
||||||
|
<text class="coupon-desc">{{ item.description || item.rules || '' }}</text>
|
||||||
|
<text class="coupon-time" v-if="item.valid_end || item.end_time">有效期至:{{ formatDate(item.valid_end || item.end_time) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="coupon-right">
|
||||||
|
<text class="coupon-status">数量:{{ item.remaining ?? item.count ?? 1 }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 任务中心弹窗 -->
|
||||||
|
<view class="popup-mask" v-if="tasksVisible" @tap="closeTasksPopup">
|
||||||
|
<view class="popup-content" @tap.stop>
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">任务中心</text>
|
||||||
|
<text class="close-btn" @tap="closeTasksPopup">×</text>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="points-list" @scrolltolower="loadMoreTasks">
|
||||||
|
<view v-if="tasksLoading && tasksList.length === 0" class="status-text">加载中...</view>
|
||||||
|
<view v-else-if="!tasksList || tasksList.length === 0" class="status-text">暂无任务</view>
|
||||||
|
<view v-for="(task, idx) in tasksList" :key="task.id || idx" class="task-item">
|
||||||
|
<view class="task-left">
|
||||||
|
<text class="task-name">{{ task.name || '任务' }}</text>
|
||||||
|
<text class="task-desc">{{ task.description || '' }}</text>
|
||||||
|
<text class="task-time">{{ formatStamp(task.start_time) }} - {{ formatStamp(task.end_time) }}</text>
|
||||||
|
<view class="reward-list" v-if="Array.isArray(task.rewards) && task.rewards.length">
|
||||||
|
<text class="reward-label">奖励</text>
|
||||||
|
<view class="reward-tags">
|
||||||
|
<view class="reward-item" v-for="(rw, ri) in task.rewards" :key="ri">
|
||||||
|
<text class="reward-type">{{ rw.reward_type || '未知' }}</text>
|
||||||
|
<text class="reward-qty">×{{ rw.quantity || 0 }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tier-list" v-if="Array.isArray(task.tiers) && task.tiers.length">
|
||||||
|
<text class="tier-label">条件</text>
|
||||||
|
<view class="tier-tags">
|
||||||
|
<view class="tier-item" v-for="(tr, ti) in task.tiers" :key="ti">
|
||||||
|
<text class="tier-text">{{ tr.metric || '-' }} {{ tr.operator || '' }} {{ tr.threshold ?? '' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="task-right">
|
||||||
|
<text class="task-status">{{ formatStatus(task.status) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="tasksLoading && tasksList.length > 0" class="loading-more">加载更多...</view>
|
||||||
|
<view v-if="!tasksHasMore && tasksList.length > 0" class="no-more">没有更多了</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { onShow, onLoad } from '@dcloudio/uni-app'
|
import { onShow, onLoad, onShareAppMessage } from '@dcloudio/uni-app'
|
||||||
import { getUserStats, getPointsBalance } from '../../api/appUser'
|
import { getUserStats, getPointsBalance, getUserPoints, getUserCoupons, redeemCoupon, getItemCards, getTasks } from '../../api/appUser'
|
||||||
|
|
||||||
const avatar = ref(uni.getStorageSync('avatar') || '')
|
const avatar = ref(uni.getStorageSync('avatar') || '')
|
||||||
const nickname = ref(uni.getStorageSync('nickname') || '')
|
const nickname = ref(uni.getStorageSync('nickname') || '')
|
||||||
@ -76,6 +229,35 @@ const stats = ref(uni.getStorageSync('user_stats') || {})
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
|
|
||||||
|
// 积分弹窗相关状态
|
||||||
|
const pointsVisible = ref(false)
|
||||||
|
const pointsList = ref([])
|
||||||
|
const pointsLoading = ref(false)
|
||||||
|
const pointsPage = ref(1)
|
||||||
|
const pointsPageSize = ref(20)
|
||||||
|
const pointsHasMore = ref(true)
|
||||||
|
|
||||||
|
// 优惠券弹窗相关状态
|
||||||
|
const couponsVisible = ref(false)
|
||||||
|
const couponsTab = ref(0) // 0: 未使用, 1: 已使用, 2: 兑换
|
||||||
|
const couponsList = ref([])
|
||||||
|
const couponsLoading = ref(false)
|
||||||
|
const couponsPage = ref(1)
|
||||||
|
const couponsHasMore = ref(true)
|
||||||
|
const redeemCode = ref('')
|
||||||
|
|
||||||
|
// 道具卡弹窗相关状态
|
||||||
|
const itemCardsVisible = ref(false)
|
||||||
|
const itemCardsList = ref([])
|
||||||
|
const itemCardsLoading = ref(false)
|
||||||
|
|
||||||
|
const tasksVisible = ref(false)
|
||||||
|
const tasksList = ref([])
|
||||||
|
const tasksLoading = ref(false)
|
||||||
|
const tasksPage = ref(1)
|
||||||
|
const tasksPageSize = ref(20)
|
||||||
|
const tasksHasMore = ref(true)
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
const token = uni.getStorageSync('token')
|
const token = uni.getStorageSync('token')
|
||||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||||
@ -127,6 +309,285 @@ function toHelp() {
|
|||||||
uni.navigateTo({ url: '/pages/help/index' })
|
uni.navigateTo({ url: '/pages/help/index' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 积分弹窗逻辑
|
||||||
|
function showPointsPopup() {
|
||||||
|
pointsVisible.value = true
|
||||||
|
// 如果没有数据,加载第一页
|
||||||
|
if (pointsList.value.length === 0) {
|
||||||
|
pointsPage.value = 1
|
||||||
|
pointsHasMore.value = true
|
||||||
|
loadPoints()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePointsPopup() {
|
||||||
|
pointsVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPoints() {
|
||||||
|
if (pointsLoading.value) return
|
||||||
|
pointsLoading.value = true
|
||||||
|
const user_id = uni.getStorageSync('user_id')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getUserPoints(user_id, pointsPage.value, pointsPageSize.value)
|
||||||
|
|
||||||
|
let list = []
|
||||||
|
let total = 0
|
||||||
|
|
||||||
|
if (res && Array.isArray(res.list)) {
|
||||||
|
list = res.list
|
||||||
|
total = res.total || 0
|
||||||
|
} else if (res && Array.isArray(res.data)) {
|
||||||
|
list = res.data
|
||||||
|
total = res.total || 0
|
||||||
|
} else if (Array.isArray(res)) {
|
||||||
|
list = res
|
||||||
|
total = res.length
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointsPage.value === 1) {
|
||||||
|
pointsList.value = list
|
||||||
|
} else {
|
||||||
|
pointsList.value = [...pointsList.value, ...list]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.length < pointsPageSize.value || (pointsPage.value * pointsPageSize.value >= total && total > 0)) {
|
||||||
|
pointsHasMore.value = false
|
||||||
|
} else {
|
||||||
|
pointsPage.value += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.length === 0 && pointsPage.value === 1) {
|
||||||
|
pointsHasMore.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Fetch points error:', e)
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
pointsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMorePoints() {
|
||||||
|
if (pointsHasMore.value && !pointsLoading.value) {
|
||||||
|
loadPoints()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateStr) {
|
||||||
|
if (!dateStr) return ''
|
||||||
|
const date = new Date(dateStr)
|
||||||
|
if (isNaN(date.getTime())) return dateStr
|
||||||
|
const y = date.getFullYear()
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const d = String(date.getDate()).padStart(2, '0')
|
||||||
|
const h = String(date.getHours()).padStart(2, '0')
|
||||||
|
const min = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
return `${y}-${m}-${d} ${h}:${min}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActionText(action) {
|
||||||
|
const map = {
|
||||||
|
'manual_add': '商品兑换积分',
|
||||||
|
'manual_sub': '系统扣除',
|
||||||
|
'register': '注册奖励',
|
||||||
|
'lottery_cost': '抽奖消耗',
|
||||||
|
'checkin': '签到奖励'
|
||||||
|
}
|
||||||
|
return map[action] || action || '积分变动'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优惠券弹窗逻辑
|
||||||
|
function showCouponsPopup() {
|
||||||
|
couponsVisible.value = true
|
||||||
|
couponsTab.value = 0
|
||||||
|
redeemCode.value = ''
|
||||||
|
couponsList.value = []
|
||||||
|
couponsPage.value = 1
|
||||||
|
couponsHasMore.value = true
|
||||||
|
loadCoupons()
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCouponsPopup() {
|
||||||
|
couponsVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchCouponsTab(index) {
|
||||||
|
if (couponsTab.value === index) return
|
||||||
|
couponsTab.value = index
|
||||||
|
if (index !== 2) {
|
||||||
|
couponsList.value = []
|
||||||
|
couponsPage.value = 1
|
||||||
|
couponsHasMore.value = true
|
||||||
|
loadCoupons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCoupons() {
|
||||||
|
if (couponsLoading.value) return
|
||||||
|
couponsLoading.value = true
|
||||||
|
const user_id = uni.getStorageSync('user_id')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const status = couponsTab.value === 0 ? 0 : 1
|
||||||
|
const res = await getUserCoupons(user_id, status, couponsPage.value, 20)
|
||||||
|
|
||||||
|
let list = []
|
||||||
|
let total = 0
|
||||||
|
|
||||||
|
if (res && Array.isArray(res.list)) {
|
||||||
|
list = res.list
|
||||||
|
total = res.total || 0
|
||||||
|
} else if (res && Array.isArray(res.data)) {
|
||||||
|
list = res.data
|
||||||
|
total = res.total || 0
|
||||||
|
} else if (Array.isArray(res)) {
|
||||||
|
list = res
|
||||||
|
total = res.length
|
||||||
|
}
|
||||||
|
|
||||||
|
if (couponsPage.value === 1) {
|
||||||
|
couponsList.value = list
|
||||||
|
} else {
|
||||||
|
couponsList.value = [...couponsList.value, ...list]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.length < 20 || (total > 0 && couponsList.value.length >= total)) {
|
||||||
|
couponsHasMore.value = false
|
||||||
|
} else {
|
||||||
|
couponsPage.value += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.length === 0 && couponsPage.value === 1) {
|
||||||
|
couponsHasMore.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Fetch coupons error:', e)
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
couponsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMoreCoupons() {
|
||||||
|
if (couponsHasMore.value && !couponsLoading.value && couponsTab.value !== 2) {
|
||||||
|
loadCoupons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRedeem() {
|
||||||
|
if (!redeemCode.value) {
|
||||||
|
uni.showToast({ title: '请输入兑换码', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const user_id = uni.getStorageSync('user_id')
|
||||||
|
try {
|
||||||
|
await redeemCoupon(user_id, redeemCode.value)
|
||||||
|
uni.showToast({ title: '兑换成功', icon: 'success' })
|
||||||
|
redeemCode.value = ''
|
||||||
|
refresh() // 刷新用户信息,可能增加了卡券
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: e.message || '兑换失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 道具卡弹窗逻辑
|
||||||
|
function showItemCardsPopup() {
|
||||||
|
itemCardsVisible.value = true
|
||||||
|
loadItemCards()
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeItemCardsPopup() {
|
||||||
|
itemCardsVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadItemCards() {
|
||||||
|
if (itemCardsLoading.value) return
|
||||||
|
itemCardsLoading.value = true
|
||||||
|
const user_id = uni.getStorageSync('user_id')
|
||||||
|
try {
|
||||||
|
const res = await getItemCards(user_id)
|
||||||
|
let list = []
|
||||||
|
if (res && Array.isArray(res.list)) {
|
||||||
|
list = res.list
|
||||||
|
} else if (res && Array.isArray(res.data)) {
|
||||||
|
list = res.data
|
||||||
|
} else if (Array.isArray(res)) {
|
||||||
|
list = res
|
||||||
|
}
|
||||||
|
itemCardsList.value = list
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Fetch item cards error:', e)
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
itemCardsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTasksPopup() {
|
||||||
|
tasksVisible.value = true
|
||||||
|
if (tasksList.value.length === 0) {
|
||||||
|
tasksPage.value = 1
|
||||||
|
tasksHasMore.value = true
|
||||||
|
loadTasks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeTasksPopup() { tasksVisible.value = false }
|
||||||
|
|
||||||
|
async function loadTasks() {
|
||||||
|
if (tasksLoading.value) return
|
||||||
|
tasksLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getTasks(tasksPage.value, tasksPageSize.value)
|
||||||
|
let list = []
|
||||||
|
let total = 0
|
||||||
|
if (res && Array.isArray(res.list)) { list = res.list; total = res.total || 0 }
|
||||||
|
else if (res && Array.isArray(res.data)) { list = res.data; total = res.total || 0 }
|
||||||
|
else if (Array.isArray(res)) { list = res; total = res.length }
|
||||||
|
if (tasksPage.value === 1) tasksList.value = list
|
||||||
|
else tasksList.value = [...tasksList.value, ...list]
|
||||||
|
if (list.length < tasksPageSize.value || (tasksPage.value * tasksPageSize.value >= total && total > 0)) tasksHasMore.value = false
|
||||||
|
else tasksPage.value += 1
|
||||||
|
if (list.length === 0 && tasksPage.value === 1) tasksHasMore.value = false
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
tasksLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMoreTasks() {
|
||||||
|
if (tasksHasMore.value && !tasksLoading.value) loadTasks()
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatStamp(ts) {
|
||||||
|
if (ts === undefined || ts === null || ts === '') return ''
|
||||||
|
const n = Number(ts)
|
||||||
|
if (isNaN(n)) return ''
|
||||||
|
const ms = n > 1e12 ? n : n * 1000
|
||||||
|
const d = new Date(ms)
|
||||||
|
if (isNaN(d.getTime())) return ''
|
||||||
|
const y = d.getFullYear()
|
||||||
|
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||||
|
const da = String(d.getDate()).padStart(2, '0')
|
||||||
|
const h = String(d.getHours()).padStart(2, '0')
|
||||||
|
const min = String(d.getMinutes()).padStart(2, '0')
|
||||||
|
return `${y}-${m}-${da} ${h}:${min}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatStatus(s) {
|
||||||
|
const n = Number(s)
|
||||||
|
if (n === 1) return '进行中'
|
||||||
|
if (n === 2) return '已结束'
|
||||||
|
if (n === 0) return '未开始'
|
||||||
|
return '未知'
|
||||||
|
}
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
avatar.value = uni.getStorageSync('avatar') || avatar.value
|
avatar.value = uni.getStorageSync('avatar') || avatar.value
|
||||||
nickname.value = uni.getStorageSync('nickname') || nickname.value
|
nickname.value = uni.getStorageSync('nickname') || nickname.value
|
||||||
@ -143,6 +604,14 @@ onShow(() => {
|
|||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
refresh()
|
refresh()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onShareAppMessage(() => {
|
||||||
|
return {
|
||||||
|
title: '邀请你一起来加入这个宝藏小程序',
|
||||||
|
path: `/pages/index/index?invite_code=${inviteCode.value}`,
|
||||||
|
imageUrl: '/static/logo.png'
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -156,6 +625,17 @@ onLoad(() => {
|
|||||||
.info-item { display: flex; justify-content: space-between; margin-bottom: 12rpx }
|
.info-item { display: flex; justify-content: space-between; margin-bottom: 12rpx }
|
||||||
.info-label { color: #666; font-size: 24rpx }
|
.info-label { color: #666; font-size: 24rpx }
|
||||||
.info-value { font-size: 28rpx }
|
.info-value { font-size: 28rpx }
|
||||||
|
.invite-value-wrapper { display: flex; align-items: center; }
|
||||||
|
.share-btn {
|
||||||
|
margin-left: 10rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
line-height: 48rpx;
|
||||||
|
background-color: #007AFF;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
}
|
||||||
.stats { display: flex; background: #fafafa; border-radius: 12rpx; padding: 20rpx; justify-content: space-between; margin-bottom: 20rpx }
|
.stats { display: flex; background: #fafafa; border-radius: 12rpx; padding: 20rpx; justify-content: space-between; margin-bottom: 20rpx }
|
||||||
.stat { flex: 1; align-items: center }
|
.stat { flex: 1; align-items: center }
|
||||||
.stat-label { color: #666; font-size: 24rpx }
|
.stat-label { color: #666; font-size: 24rpx }
|
||||||
@ -171,6 +651,248 @@ onLoad(() => {
|
|||||||
.addresses-entry { display: flex; justify-content: space-between; align-items: center; background: #f7f7f7; border-radius: 12rpx; padding: 20rpx }
|
.addresses-entry { display: flex; justify-content: space-between; align-items: center; background: #f7f7f7; border-radius: 12rpx; padding: 20rpx }
|
||||||
.addresses-text { font-size: 28rpx }
|
.addresses-text { font-size: 28rpx }
|
||||||
.addresses-arrow { font-size: 28rpx; color: #999 }
|
.addresses-arrow { font-size: 28rpx; color: #999 }
|
||||||
.refresh { width: 100%; margin-top: 12rpx }
|
.refresh-btn { margin-left: auto; padding: 10rpx }
|
||||||
|
.refresh-icon { font-size: 40rpx; color: #666; display: block }
|
||||||
|
.loading .refresh-icon { animation: rotate 1s linear infinite }
|
||||||
|
@keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||||
.error { color: #e43; margin-top: 20rpx }
|
.error { color: #e43; margin-top: 20rpx }
|
||||||
</style>
|
|
||||||
|
/* 积分弹窗样式 */
|
||||||
|
.popup-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 999;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 70vh;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-header {
|
||||||
|
padding: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #999;
|
||||||
|
padding: 0 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 30rpx;
|
||||||
|
box-sizing: border-box; /* 确保 padding 不增加宽度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
width: 100%; /* 确保子项占满 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1; /* 占据剩余空间 */
|
||||||
|
min-width: 0; /* 关键:允许 flex 子项缩小,防止内容撑开 */
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-desc {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-time {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-right {
|
||||||
|
flex-shrink: 0; /* 防止被压缩 */
|
||||||
|
margin-left: auto; /* 靠右对齐 */
|
||||||
|
text-align: right;
|
||||||
|
max-width: 40%; /* 限制最大宽度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-amount {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-amount.positive {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-amount.negative {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 60rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-more, .no-more {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优惠券弹窗样式 */
|
||||||
|
.popup-tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
.popup-tab {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.popup-tab.active {
|
||||||
|
color: #007AFF;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.popup-tab.active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 40rpx;
|
||||||
|
height: 4rpx;
|
||||||
|
background-color: #007AFF;
|
||||||
|
border-radius: 2rpx;
|
||||||
|
}
|
||||||
|
.redeem-container {
|
||||||
|
padding: 40rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.redeem-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 80rpx;
|
||||||
|
border: 1rpx solid #ddd;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.redeem-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
background-color: #007AFF;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 32rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
}
|
||||||
|
.coupon-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin: 20rpx 0;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
.coupon-left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.coupon-name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.coupon-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
.coupon-time {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
.coupon-right {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
.coupon-amount-wrapper {
|
||||||
|
color: #ff4d4f;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.symbol {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.amount-value {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.coupon-status {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item { display: flex; justify-content: space-between; align-items: flex-start; padding: 24rpx; margin: 20rpx 0; background: #f9f9f9; border-radius: 12rpx; border: 1rpx solid #eee }
|
||||||
|
.task-left { flex: 1; display: flex; flex-direction: column }
|
||||||
|
.task-name { font-size: 30rpx; font-weight: 700; color: #333 }
|
||||||
|
.task-desc { margin-top: 6rpx; font-size: 24rpx; color: #666 }
|
||||||
|
.task-time { margin-top: 6rpx; font-size: 22rpx; color: #999 }
|
||||||
|
.reward-list { margin-top: 10rpx }
|
||||||
|
.reward-label, .tier-label { font-size: 24rpx; color: #666 }
|
||||||
|
.reward-tags, .tier-tags { margin-top: 8rpx; display: flex; flex-wrap: wrap; gap: 8rpx }
|
||||||
|
.reward-item, .tier-item { background: #fff; border: 1rpx solid #eee; border-radius: 999rpx; padding: 8rpx 12rpx; display: flex; align-items: center; gap: 8rpx }
|
||||||
|
.reward-type { font-size: 24rpx; color: #333 }
|
||||||
|
.reward-qty { font-size: 22rpx; color: #999 }
|
||||||
|
.tier-text { font-size: 24rpx; color: #333 }
|
||||||
|
.task-right { margin-left: 12rpx; display: flex; align-items: center }
|
||||||
|
.task-status { font-size: 26rpx; color: #007AFF }
|
||||||
|
</style>
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="order-right">
|
<view class="order-right">
|
||||||
<view class="order-amount">{{ formatAmount(item.total_amount || item.amount || item.price) }}</view>
|
<view class="order-amount">{{ formatAmount(item.total_amount || item.amount || item.price) }}</view>
|
||||||
<view class="order-status">{{ statusText(item.status || item.pay_status || item.state) }}</view>
|
<view class="order-status">{{ statusText(item) }}</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="loadingMore" class="loading">加载中...</view>
|
<view v-if="loadingMore" class="loading">加载中...</view>
|
||||||
@ -50,13 +50,18 @@ function formatAmount(a) {
|
|||||||
if (a === undefined || a === null) return ''
|
if (a === undefined || a === null) return ''
|
||||||
const n = Number(a)
|
const n = Number(a)
|
||||||
if (Number.isNaN(n)) return String(a)
|
if (Number.isNaN(n)) return String(a)
|
||||||
return `¥${n.toFixed(2)}`
|
const yuan = n / 100
|
||||||
|
return `¥${yuan.toFixed(2)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusText(s) {
|
function statusText(item) {
|
||||||
const v = String(s || '').toLowerCase()
|
const v = item && (item.is_draw ?? item.drawed ?? item.completed)
|
||||||
if (v.includes('pend')) return '待付款'
|
const ok = v === true || v === 1 || String(v) === 'true' || String(v) === '1'
|
||||||
if (v.includes('paid') || v.includes('complete') || v.includes('done')) return '已完成'
|
if (ok) return '已完成'
|
||||||
|
const s = item && (item.status || item.pay_status || item.state)
|
||||||
|
const t = String(s || '').toLowerCase()
|
||||||
|
if (t.includes('pend')) return '待付款'
|
||||||
|
if (t.includes('paid') || t.includes('complete') || t.includes('done')) return '已完成'
|
||||||
return s || ''
|
return s || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,15 +92,20 @@ async function fetchOrders(append) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (append) {
|
if (!append) {
|
||||||
|
if (currentTab.value === 'completed') {
|
||||||
|
await fetchAllOrders()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
loading.value = true
|
||||||
|
page.value = 1
|
||||||
|
hasMore.value = true
|
||||||
|
orders.value = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (!hasMore.value || loadingMore.value) return
|
if (!hasMore.value || loadingMore.value) return
|
||||||
loadingMore.value = true
|
loadingMore.value = true
|
||||||
page.value = page.value + 1
|
page.value = page.value + 1
|
||||||
} else {
|
|
||||||
loading.value = true
|
|
||||||
page.value = 1
|
|
||||||
hasMore.value = true
|
|
||||||
orders.value = []
|
|
||||||
}
|
}
|
||||||
error.value = ''
|
error.value = ''
|
||||||
try {
|
try {
|
||||||
@ -119,6 +129,29 @@ async function fetchOrders(append) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchAllOrders() {
|
||||||
|
const user_id = uni.getStorageSync('user_id')
|
||||||
|
loading.value = true
|
||||||
|
page.value = 1
|
||||||
|
hasMore.value = false
|
||||||
|
orders.value = []
|
||||||
|
try {
|
||||||
|
const first = await getOrders(user_id, apiStatus(), 1, pageSize.value)
|
||||||
|
const itemsFirst = Array.isArray(first) ? first : (first && first.items) || (first && first.list) || []
|
||||||
|
const total = (first && first.total) || 0
|
||||||
|
orders.value = itemsFirst
|
||||||
|
const totalPages = Math.max(1, Math.ceil(Number(total) / pageSize.value))
|
||||||
|
for (let p = 2; p <= totalPages; p++) {
|
||||||
|
const res = await getOrders(user_id, apiStatus(), p, pageSize.value)
|
||||||
|
const items = Array.isArray(res) ? res : (res && res.items) || (res && res.list) || []
|
||||||
|
orders.value = orders.value.concat(items)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e && (e.message || e.errMsg) || '获取订单失败'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
onLoad((opts) => {
|
onLoad((opts) => {
|
||||||
const s = (opts && opts.status) || ''
|
const s = (opts && opts.status) || ''
|
||||||
if (s === 'completed' || s === 'pending') currentTab.value = s
|
if (s === 'completed' || s === 'pending') currentTab.value = s
|
||||||
@ -146,4 +179,4 @@ onReachBottom(() => {
|
|||||||
.error { color: #e43; margin-bottom: 12rpx }
|
.error { color: #e43; margin-bottom: 12rpx }
|
||||||
.loading { text-align: center; color: #666; margin: 20rpx 0 }
|
.loading { text-align: center; color: #666; margin: 20rpx 0 }
|
||||||
.end { text-align: center; color: #999; margin: 20rpx 0 }
|
.end { text-align: center; color: #999; margin: 20rpx 0 }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<view v-if="showNotice" class="notice-mask">
|
<view v-if="showNotice" class="notice-mask" @touchmove.stop.prevent @tap.stop>
|
||||||
<view class="notice-dialog">
|
<view class="notice-dialog" @tap.stop>
|
||||||
<view class="notice-title">提示</view>
|
<view class="notice-title">提示</view>
|
||||||
<view class="notice-content">由于价格浮动,当前暂不支持自行兑换商品,兑换请联系客服核对价格。</view>
|
<view class="notice-content">由于价格浮动,当前暂不支持自行兑换商品,兑换请联系客服核对价格。</view>
|
||||||
<view class="notice-actions">
|
<view class="notice-actions">
|
||||||
<view class="notice-check" @tap="toggleHideForever">
|
<view class="notice-check" @tap.stop="toggleHideForever">
|
||||||
<view class="check-box" :class="{ on: hideForever }"></view>
|
<view class="check-box" :class="{ on: hideForever }"></view>
|
||||||
<text class="check-text">不再显示</text>
|
<text class="check-text">不再显示</text>
|
||||||
</view>
|
</view>
|
||||||
<button class="notice-btn" type="primary" @tap="onDismissNotice">我知道了</button>
|
<button class="notice-btn" type="primary" @tap.stop="onDismissNotice">我知道了</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -151,7 +151,7 @@ function normalizeProducts(list) {
|
|||||||
id: i.id ?? i.productId ?? i._id ?? i.sku_id ?? String(idx),
|
id: i.id ?? i.productId ?? i._id ?? i.sku_id ?? String(idx),
|
||||||
image: cleanUrl(i.main_image ?? i.imageUrl ?? i.image_url ?? i.image ?? i.img ?? i.pic ?? ''),
|
image: cleanUrl(i.main_image ?? i.imageUrl ?? i.image_url ?? i.image ?? i.img ?? i.pic ?? ''),
|
||||||
title: i.title ?? i.name ?? i.product_name ?? i.sku_name ?? '',
|
title: i.title ?? i.name ?? i.product_name ?? i.sku_name ?? '',
|
||||||
price: i.price_sale ?? i.price ?? i.price_min ?? i.amount ?? null,
|
price: (() => { const raw = i.price_sale ?? i.price ?? i.price_min ?? i.amount ?? null; return raw == null ? null : (Number(raw) / 100) })(),
|
||||||
points: i.points_required ?? i.points ?? i.integral ?? null,
|
points: i.points_required ?? i.points ?? i.integral ?? null,
|
||||||
stock: i.stock ?? i.inventory ?? i.quantity ?? null,
|
stock: i.stock ?? i.inventory ?? i.quantity ?? null,
|
||||||
link: cleanUrl(i.linkUrl ?? i.link_url ?? i.link ?? i.url ?? '')
|
link: cleanUrl(i.linkUrl ?? i.link_url ?? i.link ?? i.url ?? '')
|
||||||
@ -159,12 +159,9 @@ function normalizeProducts(list) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onProductTap(p) {
|
function onProductTap(p) {
|
||||||
const imgs = (Array.isArray(products.value) ? products.value : []).map(x => x.image).filter(Boolean)
|
const id = p && p.id
|
||||||
const current = p && p.image
|
if (id !== undefined && id !== null && id !== '') {
|
||||||
if (current) {
|
uni.navigateTo({ url: `/pages/shop/detail?id=${id}` })
|
||||||
skipReloadOnce.value = true
|
|
||||||
try { uni.setStorageSync('shop_skip_reload_once', '1') } catch (_) {}
|
|
||||||
uni.previewImage({ urls: imgs.length ? imgs : [current], current })
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (p.link && /^\/.+/.test(p.link)) {
|
if (p.link && /^\/.+/.test(p.link)) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user