2704 lines
72 KiB
Plaintext
2704 lines
72 KiB
Plaintext
<!-- 消费者端 - 个人中心 -->
|
||
<template>
|
||
<view class="consumer-profile" :style="profilePageStyle">
|
||
<scroll-view class="profile-scroll-content" :style="profileScrollStyle" :scroll-y="true" :lower-threshold="120" @scroll="onRecommendScroll" @scrolltolower="onRecommendScrollToLower">
|
||
<view class="profile-page-inner">
|
||
<view class="jd-profile-top" :class="{ 'jd-profile-top-android': isAndroidApp }" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||
<view class="jd-user-header" :style="{ marginTop: userHeaderTopOffset + 'px', paddingRight: userHeaderRightReserve + 'px' }">
|
||
<view class="jd-user-brief" @click="editProfile">
|
||
<image :src="userInfo.avatar_url != '' ? userInfo.avatar_url : '/static/images/default.png'" class="jd-user-avatar" mode="aspectFill" />
|
||
<view class="jd-user-texts">
|
||
<view class="jd-user-name-row">
|
||
<text class="jd-user-name">{{ userInfo.nickname != '' ? userInfo.nickname : (userInfo.phone != '' ? userInfo.phone : '立即登录') }}</text>
|
||
</view>
|
||
|
||
<view class="jd-user-tag-row">
|
||
<text class="jd-user-tag">{{ getUserLevel() + ' >' }}</text>
|
||
<text class="jd-user-tag jd-user-tag-secondary">{{ userInfo.phone != '' ? '账号资料 >' : '完善个人资料 >' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="capsule-below-actions" :style="{ top: topActionsTop + 'px', right: topActionsRight + 'px', width: topActionsWidth + 'px' }">
|
||
<view class="capsule-action-item" @click.stop="goToAddress">
|
||
<image v-if="addressIconPath != ''" class="capsule-action-icon" :src="addressIconPath" mode="aspectFit" />
|
||
<text v-else class="capsule-action-fallback">址</text>
|
||
<text class="capsule-action-text">地址</text>
|
||
</view>
|
||
|
||
<view class="capsule-action-item" @click.stop="goToSettings">
|
||
<image v-if="settingIconPath != ''" class="capsule-action-icon" :src="settingIconPath" mode="aspectFit" />
|
||
<text v-else class="capsule-action-fallback">设</text>
|
||
<text class="capsule-action-text">设置</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="jd-wallet-card">
|
||
<view class="wallet-plus-ribbon" :class="{ 'wallet-plus-ribbon-android': isAndroidApp }">
|
||
<view class="wallet-plus-left-oval"></view>
|
||
</view>
|
||
|
||
<view class="wallet-title-block" @click="goToWallet">
|
||
<text class="wallet-title-text">我的钱包</text>
|
||
<text class="wallet-title-arrow">›</text>
|
||
</view>
|
||
|
||
<view class="wallet-curve-cover"></view>
|
||
|
||
<view class="wallet-plus-content" @click="goToMember">
|
||
<text class="wallet-crown-icon">♛</text>
|
||
<text class="wallet-plus-title">{{ getUserLevel() }}</text>
|
||
<text class="wallet-plus-desc">会员福利仅在此</text>
|
||
<view class="wallet-plus-arrow">
|
||
<text class="wallet-plus-arrow-text">›</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="wallet-assets-row">
|
||
<view class="wallet-asset-item" @click="goToPoints">
|
||
<text class="wallet-asset-value">{{ userStats.points }}</text>
|
||
<text class="wallet-asset-label">积分</text>
|
||
</view>
|
||
|
||
<view class="wallet-asset-item" @click="goToBalance">
|
||
<text class="wallet-asset-value">¥{{ userStats.balance }}</text>
|
||
<text class="wallet-asset-label">余额</text>
|
||
</view>
|
||
|
||
<view class="wallet-asset-item" @click="goToCoupons">
|
||
<text class="wallet-asset-value">{{ serviceCounts.coupons }}</text>
|
||
<text class="wallet-asset-label">优惠券</text>
|
||
</view>
|
||
|
||
<view class="wallet-asset-item" @click="goToMember">
|
||
<text class="wallet-asset-value">8折</text>
|
||
<text class="wallet-asset-label">会员权益</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="wallet-more-entry" @click="goToWallet">
|
||
<text class="wallet-more-text">更多</text>
|
||
<view class="wallet-more-circle">
|
||
<text class="wallet-more-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="order-shortcuts order-card">
|
||
<view class="order-header">
|
||
<text class="order-title">我的订单</text>
|
||
<view class="order-all-link" @click="goToOrders('all')">
|
||
<text class="card-link">全部</text>
|
||
<text class="order-all-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
<view class="order-actions">
|
||
<view class="order-action" :class="{ active: currentOrderTab === 'pending' }" @click="openOrderAction('pending', 'pending')">
|
||
<view class="order-icon-wrap">
|
||
<image class="order-icon-img" src="/static/consumer/payment.png" mode="aspectFit" />
|
||
<text v-if="orderCounts.pending > 0" class="order-badge">{{ formatOrderBadgeCount(orderCounts.pending) }}</text>
|
||
</view>
|
||
<text class="order-action-text">待付款</text>
|
||
</view>
|
||
<view class="order-action" @click="goToRefund">
|
||
<view class="order-icon-wrap">
|
||
<image class="order-icon-img" src="/static/consumer/refund_quota.png" mode="aspectFit" />
|
||
</view>
|
||
<text class="order-action-text">售后/退换</text>
|
||
</view>
|
||
<view class="order-action" :class="{ active: currentOrderTab === 'shipped' }" @click="openOrderAction('shipped', 'shipped')">
|
||
<view class="order-icon-wrap">
|
||
<image class="order-icon-img" src="/static/consumer/receipt.png" mode="aspectFit" />
|
||
<text v-if="orderCounts.shipped > 0" class="order-badge">{{ formatOrderBadgeCount(orderCounts.shipped) }}</text>
|
||
</view>
|
||
<text class="order-action-text">待收货</text>
|
||
</view>
|
||
<view class="order-action" :class="{ active: currentOrderTab === 'review' }" @click="openOrderAction('review', 'review')">
|
||
<view class="order-icon-wrap">
|
||
<image class="order-icon-img" src="/static/consumer/comment.png" mode="aspectFit" />
|
||
<text v-if="orderCounts.review > 0" class="order-badge">{{ formatOrderBadgeCount(orderCounts.review) }}</text>
|
||
</view>
|
||
<text class="order-action-text">待评价</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="pendingReceiptGoods != null" class="pending-receipt-strip" @click="openOrderAction('shipped', 'shipped')">
|
||
<image class="pending-goods-img" :src="pendingReceiptGoods.image" mode="aspectFill" />
|
||
<view class="pending-goods-info">
|
||
<view class="pending-goods-title-row">
|
||
<text class="pending-status">待收货</text>
|
||
<text class="pending-desc">{{ pendingReceiptGoods.statusText }}</text>
|
||
</view>
|
||
<text class="pending-goods-name">{{ pendingReceiptGoods.name }}</text>
|
||
</view>
|
||
<text class="pending-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="my-services service-card">
|
||
<scroll-view class="service-scroll" direction="horizontal" scroll-x="true" :scroll-y="false" :show-scrollbar="false" scroll-with-animation>
|
||
<view class="service-row">
|
||
<view class="service-item" @click="goToMessages">
|
||
<view class="service-icon-wrap">
|
||
<text class="service-text-icon">聊</text>
|
||
<text v-if="serviceCounts.unreadMessages > 0" class="service-badge">{{ serviceCounts.unreadMessages }}</text>
|
||
</view>
|
||
<text class="service-name">消息中心</text>
|
||
</view>
|
||
<view class="service-item" @click="goToCoupons">
|
||
<view class="service-icon-wrap">
|
||
<image class="service-icon-img" src="/static/consumer/coupon.png" mode="aspectFit" />
|
||
<text v-if="serviceCounts.coupons > 0" class="service-badge">{{ serviceCounts.coupons }}</text>
|
||
</view>
|
||
<text class="service-name">优惠券</text>
|
||
</view>
|
||
<view class="service-item" @click="goToAddress">
|
||
<view class="service-icon-wrap">
|
||
<text class="service-text-icon">址</text>
|
||
</view>
|
||
<text class="service-name">收货地址</text>
|
||
</view>
|
||
<view class="service-item" @click="goToFavorites">
|
||
<view class="service-icon-wrap">
|
||
<image class="service-icon-img" src="/static/consumer/collection.png" mode="aspectFit" />
|
||
<text v-if="serviceCounts.favorites > 0" class="service-badge">{{ serviceCounts.favorites }}</text>
|
||
</view>
|
||
<text class="service-name">商品收藏</text>
|
||
</view>
|
||
<view class="service-item" @click="goToFootprint">
|
||
<view class="service-icon-wrap">
|
||
<image class="service-icon-img" src="/static/consumer/footprint.png" mode="aspectFit" />
|
||
</view>
|
||
<text class="service-name">浏览足迹</text>
|
||
</view>
|
||
<view class="service-item" @click="goToRefund">
|
||
<view class="service-icon-wrap">
|
||
<text class="service-text-icon">退</text>
|
||
</view>
|
||
<text class="service-name">退款售后</text>
|
||
</view>
|
||
<view class="service-item" @click="goToOrderReviews">
|
||
<view class="service-icon-wrap">
|
||
<text class="service-text-icon">评</text>
|
||
</view>
|
||
<text class="service-name">我的评价</text>
|
||
</view>
|
||
<view class="service-item" @click="goToFollowedShops">
|
||
<view class="service-icon-wrap">
|
||
<image class="service-icon-img" src="/static/consumer/store.png" mode="aspectFit" />
|
||
</view>
|
||
<text class="service-name">关注店铺</text>
|
||
</view>
|
||
<view class="service-row-end-spacer"></view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<GuessYouLike
|
||
title="猜你喜欢"
|
||
:pageSize="8"
|
||
:loadMoreKey="guessLoadMoreKey"
|
||
@productClick="handleGuessProductClick"
|
||
/>
|
||
|
||
<view class="profile-bottom-safe"></view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view v-if="!isLoggedIn" class="guest-login-bar">
|
||
<view class="guest-login-left">
|
||
<image class="guest-login-avatar" src="/static/images/default.png" mode="aspectFit" />
|
||
<text class="guest-login-text">登录一下,康养权益和订单记录同步保存~</text>
|
||
</view>
|
||
<button class="guest-login-btn" @click.stop="goProfileLogin">立即登录</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { UserType } from '@/types/mall-types.uts'
|
||
import supabaseService from '@/utils/supabaseService.uts'
|
||
import { goToLogin } from '@/utils/utils.uts'
|
||
import GuessYouLike from '@/components/mall/GuessYouLike/GuessYouLike.uvue'
|
||
|
||
type UserStatsType = {
|
||
points: number
|
||
balance: number
|
||
level: number
|
||
}
|
||
|
||
type OrderCountsType = {
|
||
total: number
|
||
pending: number
|
||
toship: number
|
||
shipped: number
|
||
review: number
|
||
}
|
||
|
||
type ServiceCountsType = {
|
||
coupons: number
|
||
favorites: number
|
||
unreadMessages: number
|
||
}
|
||
|
||
type ConsumptionStatsType = {
|
||
total_amount: number
|
||
order_count: number
|
||
avg_amount: number
|
||
save_amount: number
|
||
}
|
||
|
||
type StatsPeriodType = {
|
||
key: string
|
||
label: string
|
||
}
|
||
|
||
type OrderItemType = {
|
||
id: string
|
||
order_no: string
|
||
status: number
|
||
actual_amount: number
|
||
created_at: string
|
||
ml_order_items: any | null
|
||
ml_shops: any | null
|
||
items_count: number
|
||
}
|
||
|
||
type RecommendProductType = {
|
||
id: string
|
||
name: string
|
||
price: number
|
||
image: string
|
||
tag: string
|
||
}
|
||
|
||
type PendingReceiptGoodsType = {
|
||
image: string
|
||
statusText: string
|
||
name: string
|
||
}
|
||
|
||
type ModalSuccessResult = { confirm: boolean; cancel: boolean }
|
||
|
||
export default {
|
||
components: {
|
||
GuessYouLike
|
||
},
|
||
data() {
|
||
return {
|
||
userInfo: {
|
||
id: '',
|
||
phone: '',
|
||
email: '',
|
||
nickname: '',
|
||
avatar_url: '',
|
||
gender: 0,
|
||
user_type: 0,
|
||
status: 0,
|
||
created_at: ''
|
||
} as UserType,
|
||
userStats: {
|
||
points: 0,
|
||
balance: 0,
|
||
level: 0
|
||
} as UserStatsType,
|
||
orderCounts: {
|
||
total: 0,
|
||
pending: 0,
|
||
toship: 0,
|
||
shipped: 0,
|
||
review: 0
|
||
} as OrderCountsType,
|
||
serviceCounts: {
|
||
coupons: 0,
|
||
favorites: 0,
|
||
unreadMessages: 0
|
||
} as ServiceCountsType,
|
||
recentOrders: [] as Array<OrderItemType>,
|
||
statsPeriods: [
|
||
{ key: 'month', label: '本月' },
|
||
{ key: 'quarter', label: '本季度' },
|
||
{ key: 'year', label: '本年' },
|
||
{ key: 'all', label: '全部' }
|
||
] as Array<StatsPeriodType>,
|
||
activeStatsPeriod: 'month',
|
||
currentStats: {
|
||
total_amount: 0,
|
||
order_count: 0,
|
||
avg_amount: 0,
|
||
save_amount: 0
|
||
} as ConsumptionStatsType,
|
||
recommendProducts: [] as Array<RecommendProductType>,
|
||
recommendPage: 1,
|
||
recommendPageSize: 8,
|
||
recommendLoading: false,
|
||
recommendHasMore: true,
|
||
recommendInitialized: false,
|
||
lastRecommendLoadTime: 0,
|
||
recommendPendingLoad: false,
|
||
recommendBottomLocked: false,
|
||
recommendViewportHeight: 0,
|
||
pageWindowHeight: 0,
|
||
isLoggedIn: false,
|
||
guessLoadMoreKey: 0,
|
||
statusBarHeight: 0,
|
||
isAndroidApp: false,
|
||
capsuleTop: 0,
|
||
capsuleBottom: 0,
|
||
capsuleLeft: 0,
|
||
capsuleRight: 0,
|
||
capsuleWidth: 0,
|
||
capsuleHeight: 0,
|
||
capsuleRightGap: 16,
|
||
heroContentTop: 0,
|
||
topActionsTop: 0,
|
||
topActionsRight: 16,
|
||
topActionsWidth: 104,
|
||
userHeaderRightReserve: 118,
|
||
userHeaderTopOffset: 0,
|
||
addressIconPath: '/static/location.png',
|
||
settingIconPath: '/static/setting.png',
|
||
navBarRight: 0, // 导航栏右侧预留空间(小程序胶囊按钮)
|
||
currentOrderTab: 'all' as string,
|
||
pendingReceiptGoods: {
|
||
image: '/static/consumer/receipt.png',
|
||
statusText: '商品运输中',
|
||
name: '您有 1 件商品正在配送,请注意查收'
|
||
} as PendingReceiptGoodsType | null,
|
||
allOrders: [] as Array<OrderItemType>
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.initPage()
|
||
this.refreshLoginState()
|
||
this.loadUserProfile()
|
||
this.loadOrders()
|
||
|
||
// 监听订单更新事件
|
||
uni.$on('orderUpdated', this.handleOrderUpdated)
|
||
uni.$on('authChanged', this.handleAuthChanged)
|
||
},
|
||
onShow() {
|
||
this.refreshLoginState()
|
||
this.refreshData()
|
||
},
|
||
onUnload() {
|
||
// 移除事件监听
|
||
uni.$off('orderUpdated', this.handleOrderUpdated)
|
||
uni.$off('authChanged', this.handleAuthChanged)
|
||
},
|
||
computed: {
|
||
filteredOrders(): Array<OrderItemType> {
|
||
const result: Array<OrderItemType> = []
|
||
if (this.currentOrderTab === 'all') {
|
||
for (let i: number = 0; i < this.allOrders.length; i++) {
|
||
result.push(this.allOrders[i])
|
||
}
|
||
return result
|
||
}
|
||
let targetStatus: number = 0
|
||
if (this.currentOrderTab === 'pending') {
|
||
targetStatus = 1
|
||
} else if (this.currentOrderTab === 'toship') {
|
||
targetStatus = 2
|
||
} else if (this.currentOrderTab === 'shipped') {
|
||
targetStatus = 3
|
||
} else if (this.currentOrderTab === 'review') {
|
||
targetStatus = 4
|
||
} else {
|
||
return result
|
||
}
|
||
for (let i: number = 0; i < this.allOrders.length; i++) {
|
||
if (this.allOrders[i].status === targetStatus) {
|
||
result.push(this.allOrders[i])
|
||
}
|
||
}
|
||
return result
|
||
},
|
||
profilePageStyle(): string {
|
||
return this.getProfilePageStyle()
|
||
},
|
||
profileScrollStyle(): string {
|
||
return this.getProfileScrollStyle()
|
||
}
|
||
},
|
||
methods: {
|
||
refreshLoginState() {
|
||
const userId = supabaseService.getCurrentUserId()
|
||
this.isLoggedIn = userId != null && userId !== ''
|
||
},
|
||
|
||
toRecommendScrollJson(value: any | null): UTSJSONObject | null {
|
||
if (value == null) {
|
||
return null
|
||
}
|
||
if (value instanceof UTSJSONObject) {
|
||
return value as UTSJSONObject
|
||
}
|
||
const raw = JSON.stringify(value)
|
||
if (raw === '' || raw === 'null') {
|
||
return null
|
||
}
|
||
const parsed = JSON.parse(raw)
|
||
if (parsed == null) {
|
||
return null
|
||
}
|
||
return parsed as UTSJSONObject
|
||
},
|
||
|
||
readRecommendScrollMetric(detail: UTSJSONObject | null, key: string): number {
|
||
if (detail == null) {
|
||
return 0
|
||
}
|
||
const value = detail.getNumber(key)
|
||
if (value != null) {
|
||
return value
|
||
}
|
||
return 0
|
||
},
|
||
|
||
mergeRecommendProducts(oldList: Array<RecommendProductType>, newList: Array<RecommendProductType>): Array<RecommendProductType> {
|
||
const ids: Array<string> = []
|
||
const result: Array<RecommendProductType> = []
|
||
|
||
for (let i: number = 0; i < oldList.length; i++) {
|
||
ids.push(oldList[i].id)
|
||
result.push(oldList[i])
|
||
}
|
||
|
||
for (let i: number = 0; i < newList.length; i++) {
|
||
if (ids.indexOf(newList[i].id) < 0) {
|
||
ids.push(newList[i].id)
|
||
result.push(newList[i])
|
||
}
|
||
}
|
||
|
||
return result
|
||
},
|
||
|
||
async fetchRecommendProducts(page: number, pageSize: number): Promise<Array<RecommendProductType>> {
|
||
console.log('[profile推荐] fetchRecommendProducts 请求 page=', page, 'pageSize=', pageSize)
|
||
const result = await supabaseService.searchProducts('', page, pageSize, 'sales')
|
||
console.log('[profile推荐] fetchRecommendProducts 完成 total=', result.total, 'hasmore=', result.hasmore, 'dataLength=', result.data.length)
|
||
const rawList = result.data as any[]
|
||
const list: Array<RecommendProductType> = []
|
||
for (let i: number = 0; i < rawList.length; i++) {
|
||
const productObj = JSON.parse(JSON.stringify(rawList[i])) as UTSJSONObject
|
||
const productId = productObj.getString('id') ?? ''
|
||
if (productId === '') {
|
||
continue
|
||
}
|
||
const basePrice = productObj.getNumber('base_price')
|
||
const marketPrice = productObj.getNumber('market_price')
|
||
const mainImage = productObj.getString('main_image_url')
|
||
const fallbackImage = productObj.getString('image_url')
|
||
const sales = productObj.getNumber('sales')
|
||
list.push({
|
||
id: productId,
|
||
name: productObj.getString('name') ?? '精选商品',
|
||
image: mainImage != null && mainImage !== '' ? mainImage : (fallbackImage != null && fallbackImage !== '' ? fallbackImage : '/static/images/default.png'),
|
||
price: basePrice != null ? basePrice : (marketPrice != null ? marketPrice : 0),
|
||
tag: sales != null && sales > 0 ? ('已售' + sales) : '猜你喜欢'
|
||
} as RecommendProductType)
|
||
}
|
||
return list
|
||
},
|
||
|
||
async loadRecommendProducts(reset: boolean) {
|
||
console.log('[profile推荐] loadRecommendProducts 入口 reset=', reset, 'page=', this.recommendPage, 'pageSize=', this.recommendPageSize, 'loading=', this.recommendLoading, 'hasMore=', this.recommendHasMore, 'oldLength=', this.recommendProducts.length)
|
||
if (this.recommendLoading) {
|
||
console.log('[profile推荐] 跳过:正在加载中')
|
||
if (!reset) {
|
||
this.recommendPendingLoad = true
|
||
}
|
||
return
|
||
}
|
||
if (!reset && !this.recommendHasMore) {
|
||
console.log('[profile推荐] 跳过:没有更多数据')
|
||
return
|
||
}
|
||
if (!reset) {
|
||
this.lastRecommendLoadTime = Date.now()
|
||
}
|
||
|
||
this.recommendLoading = true
|
||
|
||
if (reset) {
|
||
this.recommendPage = 1
|
||
this.recommendHasMore = true
|
||
}
|
||
|
||
try {
|
||
const page = this.recommendPage
|
||
const pageSize = this.recommendPageSize
|
||
const newList = await this.fetchRecommendProducts(page, pageSize)
|
||
console.log('[profile推荐] page=', page, '返回数量=', newList.length)
|
||
console.log('[profile推荐] 返回ID=', newList.map((item) => item.id).join(','))
|
||
|
||
const beforeLength = this.recommendProducts.length
|
||
let afterList: Array<RecommendProductType> = []
|
||
|
||
if (reset) {
|
||
afterList = newList
|
||
} else {
|
||
afterList = this.mergeRecommendProducts(this.recommendProducts, newList)
|
||
}
|
||
this.recommendProducts = afterList
|
||
console.log('[profile推荐] 追加前=', beforeLength, '追加后=', this.recommendProducts.length)
|
||
if (!reset && newList.length > 0 && afterList.length === beforeLength) {
|
||
console.warn('[profile推荐] 本次返回商品全部重复,请检查 searchProducts 分页是否生效 page=', page)
|
||
}
|
||
|
||
if (newList.length < pageSize) {
|
||
this.recommendHasMore = false
|
||
} else {
|
||
this.recommendPage = this.recommendPage + 1
|
||
}
|
||
|
||
if (!reset) {
|
||
this.recommendBottomLocked = false
|
||
}
|
||
|
||
this.recommendInitialized = true
|
||
} catch (e) {
|
||
console.error('加载推荐商品失败:', e)
|
||
this.recommendHasMore = false
|
||
} finally {
|
||
this.recommendLoading = false
|
||
if (!reset && this.recommendPendingLoad && this.recommendHasMore) {
|
||
console.log('[profile推荐] 消费待续加载请求')
|
||
this.recommendPendingLoad = false
|
||
this.loadRecommendProducts(false)
|
||
}
|
||
}
|
||
},
|
||
|
||
onRecommendScrollToLower() {
|
||
this.guessLoadMoreKey = this.guessLoadMoreKey + 1
|
||
},
|
||
|
||
onRecommendScroll(event: any) {
|
||
return
|
||
},
|
||
|
||
handleGuessProductClick(productId: string) {
|
||
if (productId == null || productId === '') {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/product-detail?id=${productId}&productId=${productId}`
|
||
})
|
||
},
|
||
|
||
resetGuestProfileState() {
|
||
this.userInfo = {
|
||
id: '',
|
||
phone: '',
|
||
email: '',
|
||
nickname: '立即登录',
|
||
avatar_url: '',
|
||
gender: 0,
|
||
user_type: 0,
|
||
status: 0,
|
||
created_at: ''
|
||
} as UserType
|
||
|
||
this.userStats = {
|
||
points: 0,
|
||
balance: 0,
|
||
level: 0
|
||
} as UserStatsType
|
||
|
||
this.serviceCounts = {
|
||
coupons: 0,
|
||
favorites: 0,
|
||
unreadMessages: 0
|
||
} as ServiceCountsType
|
||
|
||
this.orderCounts = {
|
||
total: 0,
|
||
pending: 0,
|
||
toship: 0,
|
||
shipped: 0,
|
||
review: 0
|
||
} as OrderCountsType
|
||
|
||
this.recentOrders = [] as Array<OrderItemType>
|
||
this.allOrders = [] as Array<OrderItemType>
|
||
this.currentOrderTab = 'all'
|
||
this.pendingReceiptGoods = null
|
||
},
|
||
|
||
async loadOrders() {
|
||
const userId = supabaseService.getCurrentUserId()
|
||
if (userId == null || userId === '') {
|
||
this.orderCounts = {
|
||
total: 0,
|
||
pending: 0,
|
||
toship: 0,
|
||
shipped: 0,
|
||
review: 0
|
||
} as OrderCountsType
|
||
this.recentOrders = [] as Array<OrderItemType>
|
||
this.allOrders = [] as Array<OrderItemType>
|
||
this.pendingReceiptGoods = null
|
||
return
|
||
}
|
||
|
||
try {
|
||
const orders = await supabaseService.getOrders()
|
||
|
||
const mappedOrders: Array<OrderItemType> = []
|
||
for (let i: number = 0; i < orders.length; i++) {
|
||
const rawItem = orders[i]
|
||
const o = JSON.parse(JSON.stringify(rawItem)) as UTSJSONObject
|
||
|
||
let status = o.getNumber('status')
|
||
if (status == null) {
|
||
const orderStatus = o.getNumber('order_status')
|
||
status = orderStatus != null ? orderStatus : 0
|
||
}
|
||
|
||
let actualAmount = o.getNumber('actual_amount')
|
||
if (actualAmount == null) {
|
||
const totalAmount = o.getNumber('total_amount')
|
||
actualAmount = totalAmount != null ? totalAmount : 0
|
||
}
|
||
|
||
const mlOrderItems = o.get('ml_order_items')
|
||
|
||
let itemsCount = 0
|
||
if (mlOrderItems != null && Array.isArray(mlOrderItems)) {
|
||
itemsCount = (mlOrderItems as any[]).length
|
||
}
|
||
|
||
const orderItem: OrderItemType = {
|
||
id: o.getString('id') ?? '',
|
||
order_no: o.getString('order_no') ?? '',
|
||
status: status,
|
||
actual_amount: actualAmount,
|
||
created_at: o.getString('created_at') ?? '',
|
||
ml_order_items: mlOrderItems,
|
||
ml_shops: o.get('ml_shops'),
|
||
items_count: itemsCount
|
||
}
|
||
|
||
mappedOrders.push(orderItem)
|
||
}
|
||
|
||
for (let i: number = 0; i < mappedOrders.length; i++) {
|
||
for (let j: number = i + 1; j < mappedOrders.length; j++) {
|
||
const dateA = mappedOrders[i]['created_at'] as string
|
||
const dateB = mappedOrders[j]['created_at'] as string
|
||
const timeA = new Date(dateA != null ? dateA : '1970-01-01').getTime()
|
||
const timeB = new Date(dateB != null ? dateB : '1970-01-01').getTime()
|
||
if (timeA < timeB) {
|
||
const temp = mappedOrders[i]
|
||
mappedOrders[i] = mappedOrders[j]
|
||
mappedOrders[j] = temp
|
||
}
|
||
}
|
||
}
|
||
|
||
this.allOrders = mappedOrders
|
||
|
||
const recentList: Array<OrderItemType> = []
|
||
const limit = mappedOrders.length < 5 ? mappedOrders.length : 5
|
||
for (let i: number = 0; i < limit; i++) {
|
||
recentList.push(mappedOrders[i])
|
||
}
|
||
this.recentOrders = recentList
|
||
|
||
let total = 0
|
||
let pending = 0
|
||
let toship = 0
|
||
let shipped = 0
|
||
let review = 0
|
||
|
||
for (let i: number = 0; i < mappedOrders.length; i++) {
|
||
total++
|
||
const status = mappedOrders[i].status
|
||
if (status === 1) pending++
|
||
else if (status === 2) toship++
|
||
else if (status === 3) shipped++
|
||
else if (status === 4) review++
|
||
}
|
||
|
||
this.orderCounts = {
|
||
total: total,
|
||
pending: pending,
|
||
toship: toship,
|
||
shipped: shipped,
|
||
review: review
|
||
}
|
||
|
||
if (shipped > 0) {
|
||
this.pendingReceiptGoods = {
|
||
image: '/static/consumer/receipt.png',
|
||
statusText: '商品运输中',
|
||
name: '您有 ' + shipped + ' 件商品正在配送,请注意查收'
|
||
} as PendingReceiptGoodsType
|
||
} else {
|
||
this.pendingReceiptGoods = null
|
||
}
|
||
} catch (e) {
|
||
console.error('加载订单异常', e)
|
||
}
|
||
},
|
||
|
||
// 切换订单Tab
|
||
switchOrderTab(tab: string) {
|
||
this.currentOrderTab = tab
|
||
},
|
||
|
||
formatOrderBadgeCount(count: number): string {
|
||
if (count <= 0) {
|
||
return ''
|
||
}
|
||
if (count > 99) {
|
||
return '99+'
|
||
}
|
||
return count.toString()
|
||
},
|
||
|
||
openOrderAction(tab: string, type: string) {
|
||
this.switchOrderTab(tab)
|
||
this.goToOrders(type)
|
||
},
|
||
|
||
// 获取当前订单部分标题
|
||
getOrderSectionTitle(): string {
|
||
if (this.currentOrderTab === 'all') return '全部订单'
|
||
if (this.currentOrderTab === 'pending') return '待支付订单'
|
||
if (this.currentOrderTab === 'toship') return '待发货订单'
|
||
if (this.currentOrderTab === 'shipped') return '待收货订单'
|
||
if (this.currentOrderTab === 'review') return '待评价订单'
|
||
return '我的订单'
|
||
},
|
||
|
||
initPage() {
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
this.statusBarHeight = systemInfo.statusBarHeight ?? 0
|
||
this.pageWindowHeight = systemInfo.windowHeight ?? systemInfo.screenHeight ?? 0
|
||
this.recommendViewportHeight = this.pageWindowHeight
|
||
// #ifdef APP-ANDROID
|
||
this.isAndroidApp = true
|
||
// #endif
|
||
|
||
// 获取小程序胶囊按钮信息
|
||
// #ifdef MP-WEIXIN
|
||
try {
|
||
const menuButton = uni.getMenuButtonBoundingClientRect()
|
||
if (menuButton != null && menuButton.width > 0) {
|
||
this.capsuleTop = menuButton.top
|
||
this.capsuleBottom = menuButton.bottom
|
||
this.capsuleLeft = menuButton.left
|
||
this.capsuleRight = menuButton.right
|
||
this.capsuleWidth = menuButton.width
|
||
this.capsuleHeight = menuButton.height
|
||
this.capsuleRightGap = systemInfo.screenWidth - menuButton.right
|
||
this.topActionsTop = menuButton.bottom + 6
|
||
this.topActionsRight = this.capsuleRightGap
|
||
this.topActionsWidth = 104
|
||
this.userHeaderRightReserve = this.topActionsWidth + 12
|
||
this.userHeaderTopOffset = 15
|
||
this.navBarRight = systemInfo.screenWidth - menuButton.left + 10
|
||
console.log('[profile胶囊]', {
|
||
screenWidth: systemInfo.screenWidth,
|
||
capsuleTop: this.capsuleTop,
|
||
capsuleBottom: this.capsuleBottom,
|
||
capsuleLeft: this.capsuleLeft,
|
||
capsuleRight: this.capsuleRight,
|
||
topActionsTop: this.topActionsTop,
|
||
topActionsRight: this.topActionsRight
|
||
})
|
||
} else {
|
||
this.topActionsTop = this.statusBarHeight + 44
|
||
this.topActionsRight = 16
|
||
this.topActionsWidth = 104
|
||
this.userHeaderRightReserve = 116
|
||
this.userHeaderTopOffset = 8
|
||
this.navBarRight = 96
|
||
}
|
||
} catch (e) {
|
||
console.log('获取胶囊按钮信息失败', e)
|
||
this.topActionsTop = this.statusBarHeight + 44
|
||
this.topActionsRight = 16
|
||
this.topActionsWidth = 104
|
||
this.userHeaderRightReserve = 116
|
||
this.userHeaderTopOffset = 8
|
||
this.navBarRight = 96
|
||
}
|
||
// #endif
|
||
|
||
// #ifndef MP-WEIXIN
|
||
this.topActionsTop = this.statusBarHeight + 12
|
||
this.topActionsRight = 16
|
||
this.topActionsWidth = 104
|
||
this.userHeaderRightReserve = 116
|
||
this.userHeaderTopOffset = 6
|
||
this.navBarRight = 16
|
||
// #endif
|
||
},
|
||
normalizeImageUrl(url: string): string {
|
||
if (url == null || url === '') return '/static/images/default.png'
|
||
const trimmed = url.trim()
|
||
if (trimmed === '') return '/static/images/default.png'
|
||
if (trimmed.indexOf('blob:') === 0) return '/static/images/default.png'
|
||
return trimmed
|
||
},
|
||
getProfilePageStyle(): string {
|
||
if (!this.isAndroidApp) {
|
||
return ''
|
||
}
|
||
if (this.pageWindowHeight <= 0) {
|
||
return ''
|
||
}
|
||
return 'height:' + this.pageWindowHeight + 'px;min-height:' + this.pageWindowHeight + 'px;'
|
||
},
|
||
getProfileScrollStyle(): string {
|
||
if (!this.isAndroidApp) {
|
||
return ''
|
||
}
|
||
if (this.pageWindowHeight <= 0) {
|
||
return ''
|
||
}
|
||
return 'height:' + this.pageWindowHeight + 'px;min-height:' + this.pageWindowHeight + 'px;'
|
||
},
|
||
async loadUserProfile() {
|
||
this.refreshLoginState()
|
||
const userId = supabaseService.getCurrentUserId()
|
||
if (userId == null || userId === '') {
|
||
this.resetGuestProfileState()
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 获取用户资料
|
||
const profile = await supabaseService.getUserProfile()
|
||
if (profile != null) {
|
||
// 映射字段
|
||
let uId = ''
|
||
let uPhone = ''
|
||
let uEmail = ''
|
||
let uNickname = ''
|
||
let uAvatar = ''
|
||
let uGender = 0
|
||
|
||
if (profile instanceof UTSJSONObject) {
|
||
uId = profile.getString('user_id') ?? ''
|
||
uPhone = profile.getString('phone') ?? ''
|
||
uEmail = profile.getString('email') ?? ''
|
||
uNickname = profile.getString('nickname') ?? ''
|
||
uAvatar = profile.getString('avatar_url') ?? ''
|
||
uGender = profile.getNumber('gender') ?? 0
|
||
} else {
|
||
// 必须使用 JSON.parse(JSON.stringify()) 转换为 UTSJSONObject
|
||
const profileObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject
|
||
uId = profileObj.getString('user_id') ?? ''
|
||
uPhone = profileObj.getString('phone') ?? ''
|
||
uEmail = profileObj.getString('email') ?? ''
|
||
uNickname = profileObj.getString('nickname') ?? ''
|
||
uAvatar = profileObj.getString('avatar_url') ?? ''
|
||
uGender = profileObj.getNumber('gender') ?? 0
|
||
}
|
||
|
||
if (uNickname === '' && uPhone !== '') {
|
||
uNickname = uPhone.substring(0, 3) + '****' + uPhone.substring(7)
|
||
}
|
||
|
||
const safeAvatar = this.normalizeImageUrl(uAvatar)
|
||
|
||
this.userInfo = {
|
||
id: uId,
|
||
phone: uPhone,
|
||
email: uEmail,
|
||
nickname: uNickname != '' ? uNickname : '微信用户',
|
||
avatar_url: safeAvatar,
|
||
gender: uGender,
|
||
user_type: 1,
|
||
status: 1,
|
||
created_at: new Date().toISOString()
|
||
} as UserType
|
||
} else {
|
||
this.resetGuestProfileState()
|
||
this.userInfo.id = userId
|
||
this.userInfo.nickname = '用户' + userId.substring(0, 4)
|
||
}
|
||
|
||
// 获取积分和余额(顺序获取,UTS不支持Promise.all数组解构)
|
||
const balanceResult = await supabaseService.getUserBalance()
|
||
const points = await supabaseService.getUserPoints()
|
||
|
||
const balanceValue = balanceResult.getNumber('balance') ?? 0
|
||
|
||
this.userStats = {
|
||
points: points,
|
||
balance: balanceValue,
|
||
level: this.calculateLevel(points) // 根据积分计算等级
|
||
} as UserStatsType
|
||
|
||
} catch (e) {
|
||
console.error('加载用户信息失败', e)
|
||
// 保持默认或显示错误
|
||
}
|
||
},
|
||
|
||
calculateLevel(points: number): number {
|
||
if (points < 1000) return 0
|
||
if (points < 5000) return 1
|
||
if (points < 20000) return 2
|
||
if (points < 50000) return 3
|
||
return 4
|
||
},
|
||
|
||
loadConsumptionStats() {
|
||
if (this.activeStatsPeriod === 'month') {
|
||
this.currentStats = {
|
||
total_amount: 1280.50,
|
||
order_count: 8,
|
||
avg_amount: 160.06,
|
||
save_amount: 85.20
|
||
} as ConsumptionStatsType
|
||
} else if (this.activeStatsPeriod === 'quarter') {
|
||
this.currentStats = {
|
||
total_amount: 3680.80,
|
||
order_count: 18,
|
||
avg_amount: 204.49,
|
||
save_amount: 256.30
|
||
} as ConsumptionStatsType
|
||
} else if (this.activeStatsPeriod === 'year') {
|
||
this.currentStats = {
|
||
total_amount: 15680.90,
|
||
order_count: 56,
|
||
avg_amount: 280.02,
|
||
save_amount: 986.50
|
||
} as ConsumptionStatsType
|
||
} else {
|
||
this.currentStats = {
|
||
total_amount: 25680.50,
|
||
order_count: 89,
|
||
avg_amount: 288.55,
|
||
save_amount: 1580.20
|
||
} as ConsumptionStatsType
|
||
}
|
||
},
|
||
|
||
refreshData() {
|
||
this.refreshLoginState()
|
||
const userId = supabaseService.getCurrentUserId()
|
||
if (userId == null || userId === '') {
|
||
this.resetGuestProfileState()
|
||
return
|
||
}
|
||
|
||
// 刷新页面数据
|
||
this.loadUserProfile()
|
||
this.loadOrders()
|
||
this.updateCouponCount() // 更新优惠券数量
|
||
},
|
||
|
||
async updateCouponCount() {
|
||
const userId = supabaseService.getCurrentUserId()
|
||
if (userId == null || userId === '') {
|
||
this.serviceCounts.coupons = 0
|
||
return
|
||
}
|
||
|
||
// 从 Supabase 获取真实的优惠券数量
|
||
try {
|
||
const count = await supabaseService.getUserCouponCount()
|
||
this.serviceCounts.coupons = count
|
||
} catch (e) {
|
||
console.error('获取优惠券数量失败', e)
|
||
this.serviceCounts.coupons = 0
|
||
}
|
||
},
|
||
|
||
getUserLevel(): string {
|
||
const levels = ['新手', '铜牌会员', '银牌会员', '金牌会员', '钻石会员']
|
||
if (this.userStats.level >= 0 && this.userStats.level < levels.length) {
|
||
return levels[this.userStats.level]
|
||
}
|
||
return '新手'
|
||
},
|
||
|
||
getOrderStatusText(status: number): string {
|
||
if (status === 6) return '退款中'
|
||
if (status === 7) return '已退款'
|
||
const statusTexts = ['异常', '待支付', '待发货', '待收货', '已完成', '已取消']
|
||
if (status >= 0 && status < statusTexts.length) {
|
||
return statusTexts[status]
|
||
}
|
||
return '未知'
|
||
},
|
||
|
||
getOrderStatusClass(status: number): string {
|
||
if (status === 6) return 'refunding'
|
||
if (status === 7) return 'refunded'
|
||
const statusClasses = ['error', 'pending', 'processing', 'shipping', 'completed', 'cancelled']
|
||
if (status >= 0 && status < statusClasses.length) {
|
||
return statusClasses[status]
|
||
}
|
||
return 'error'
|
||
},
|
||
|
||
showOrderMenu(order: OrderItemType) {
|
||
const status = order.status
|
||
let actions: string[] = []
|
||
|
||
if (status === 1) {
|
||
actions = ['取消订单', '联系卖家']
|
||
} else if (status === 2) {
|
||
actions = ['提醒发货', '申请退款', '联系卖家']
|
||
} else if (status === 3) {
|
||
actions = ['查看物流', '确认收货', '申请退款', '联系卖家']
|
||
} else if (status === 4) {
|
||
actions = ['申请售后', '再次购买', '联系卖家']
|
||
} else if (status === 5) {
|
||
actions = ['删除订单', '再次购买', '联系卖家']
|
||
} else if (status === 6) {
|
||
actions = ['退款进度', '联系卖家']
|
||
} else if (status === 7) {
|
||
actions = ['再次购买', '联系卖家']
|
||
}
|
||
|
||
uni.showActionSheet({
|
||
itemList: actions,
|
||
success: (res: ShowActionSheetSuccess) => {
|
||
const action = actions[res.tapIndex]
|
||
this.handleOrderAction(order, action)
|
||
}
|
||
})
|
||
},
|
||
|
||
handleOrderAction(order: OrderItemType, action: string) {
|
||
if (action === '取消订单') {
|
||
this.cancelOrderAction(order)
|
||
} else if (action === '联系卖家') {
|
||
this.contactSeller(order)
|
||
} else if (action === '提醒发货') {
|
||
this.remindShipping(order)
|
||
} else if (action === '申请退款' || action === '申请售后') {
|
||
this.applyRefund(order)
|
||
} else if (action === '查看物流') {
|
||
this.viewLogistics(order.id)
|
||
} else if (action === '确认收货') {
|
||
this.confirmReceive(order)
|
||
} else if (action === '再次购买') {
|
||
this.repurchase(order)
|
||
} else if (action === '删除订单') {
|
||
this.deleteOrder(order.id)
|
||
} else if (action === '退款进度') {
|
||
this.viewRefundProgress(order.id)
|
||
}
|
||
},
|
||
|
||
cancelOrderAction(order: OrderItemType) {
|
||
uni.showModal({
|
||
title: '确认取消',
|
||
content: '确定要取消此订单吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
uni.showLoading({ title: '取消中...' })
|
||
supabaseService.cancelOrder(order.id).then(() => {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '订单已取消', icon: 'success' })
|
||
this.loadOrders()
|
||
}).catch(() => {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '取消失败', icon: 'none' })
|
||
})
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
contactSeller(order: OrderItemType) {
|
||
const merchantId = order.ml_shops != null ? this.getMerchantIdFromOrder(order) : ''
|
||
if (merchantId !== '') {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/chat?merchantId=${merchantId}`
|
||
})
|
||
} else {
|
||
uni.showToast({ title: '暂无卖家联系方式', icon: 'none' })
|
||
}
|
||
},
|
||
|
||
getMerchantIdFromOrder(order: OrderItemType): string {
|
||
const shopsRaw = order.ml_shops
|
||
if (shopsRaw != null) {
|
||
const shopStr = JSON.stringify(shopsRaw)
|
||
const shopParsed = JSON.parse(shopStr)
|
||
if (shopParsed != null) {
|
||
const shopObj = shopParsed as UTSJSONObject
|
||
return shopObj.getString('merchant_id') ?? ''
|
||
}
|
||
}
|
||
return ''
|
||
},
|
||
|
||
remindShipping(order: OrderItemType) {
|
||
uni.showLoading({ title: '提醒中...' })
|
||
const merchantId = order.ml_shops != null ? this.getMerchantIdFromOrder(order) : ''
|
||
if (merchantId !== '') {
|
||
const message = `你好,我的订单[${order.order_no}]还没有发货,请尽快安排,谢谢。`
|
||
supabaseService.sendChatMessage(message, merchantId).then(() => {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '已提醒卖家发货', icon: 'success' })
|
||
}).catch(() => {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '提醒失败', icon: 'none' })
|
||
})
|
||
} else {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '无法联系卖家', icon: 'none' })
|
||
}
|
||
},
|
||
|
||
applyRefund(order: OrderItemType) {
|
||
if (!this.ensureUserLoggedIn(`/pages/mall/consumer/apply-refund?orderId=${order.id}`)) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/apply-refund?orderId=${order.id}`
|
||
})
|
||
},
|
||
|
||
viewLogistics(orderId: string) {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/logistics?orderId=${orderId}`
|
||
})
|
||
},
|
||
|
||
repurchase(order: OrderItemType) {
|
||
if (!this.ensureUserLoggedIn('/pages/main/profile')) {
|
||
return
|
||
}
|
||
uni.showLoading({ title: '处理中...' })
|
||
const itemsRaw = order.ml_order_items
|
||
if (itemsRaw == null || (itemsRaw as any[]).length === 0) {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '订单无商品', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
const items = itemsRaw as any[]
|
||
let completed = 0
|
||
const total = items.length
|
||
let successCount = 0
|
||
|
||
for (let i = 0; i < items.length; i++) {
|
||
const itemStr = JSON.stringify(items[i])
|
||
const itemParsed = JSON.parse(itemStr)
|
||
if (itemParsed == null) {
|
||
completed++
|
||
if (completed === total) {
|
||
uni.hideLoading()
|
||
if (successCount > 0) {
|
||
uni.showToast({ title: `已添加${successCount}件商品`, icon: 'success' })
|
||
} else {
|
||
uni.showToast({ title: '添加失败', icon: 'none' })
|
||
}
|
||
}
|
||
continue
|
||
}
|
||
|
||
const itemObj = itemParsed as UTSJSONObject
|
||
const productId = itemObj.getString('product_id') ?? ''
|
||
const merchantId = order.ml_shops != null ? this.getMerchantIdFromOrder(order) : ''
|
||
|
||
if (productId !== '') {
|
||
supabaseService.addToCart(productId, 1, '', merchantId).then((success) => {
|
||
completed++
|
||
if (success) successCount++
|
||
if (completed === total) {
|
||
uni.hideLoading()
|
||
if (successCount > 0) {
|
||
uni.showToast({ title: `已添加${successCount}件商品`, icon: 'success' })
|
||
} else {
|
||
uni.showToast({ title: '添加失败', icon: 'none' })
|
||
}
|
||
}
|
||
}).catch(() => {
|
||
completed++
|
||
if (completed === total) {
|
||
uni.hideLoading()
|
||
if (successCount > 0) {
|
||
uni.showToast({ title: `已添加${successCount}件商品`, icon: 'success' })
|
||
} else {
|
||
uni.showToast({ title: '添加失败', icon: 'none' })
|
||
}
|
||
}
|
||
})
|
||
} else {
|
||
completed++
|
||
if (completed === total) {
|
||
uni.hideLoading()
|
||
if (successCount > 0) {
|
||
uni.showToast({ title: `已添加${successCount}件商品`, icon: 'success' })
|
||
} else {
|
||
uni.showToast({ title: '添加失败', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
deleteOrder(orderId: string) {
|
||
uni.showModal({
|
||
title: '删除订单',
|
||
content: '确定要删除此订单吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
uni.showLoading({ title: '删除中...' })
|
||
supabaseService.deleteOrder(orderId).then(() => {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '订单已删除', icon: 'success' })
|
||
this.loadOrders()
|
||
}).catch(() => {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '删除失败', icon: 'none' })
|
||
})
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
viewRefundProgress(orderId: string) {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/refund?orderId=${orderId}`
|
||
})
|
||
},
|
||
|
||
getOrderShopName(order: OrderItemType): string {
|
||
const shopsRaw = order.ml_shops
|
||
if (shopsRaw != null) {
|
||
const shopStr = JSON.stringify(shopsRaw)
|
||
const shopParsed = JSON.parse(shopStr)
|
||
if (shopParsed != null) {
|
||
const shopObj = shopParsed as UTSJSONObject
|
||
const name = shopObj.getString('shop_name')
|
||
if (name != null && name !== '') return name
|
||
}
|
||
}
|
||
return '自营店铺'
|
||
},
|
||
|
||
getOrderMainImage(order: OrderItemType): string {
|
||
const itemsRaw = order.ml_order_items
|
||
if (itemsRaw == null) return '/static/images/default.png'
|
||
const items = itemsRaw as any[]
|
||
if (items.length > 0) {
|
||
const firstItem = items[0]
|
||
const itemStr = JSON.stringify(firstItem)
|
||
const itemParsed = JSON.parse(itemStr)
|
||
if (itemParsed == null) return '/static/images/default.png'
|
||
const itemObj = itemParsed as UTSJSONObject
|
||
const imgUrl = itemObj.getString('image_url')
|
||
const prodImg = itemObj.getString('product_image')
|
||
const img = (imgUrl != null && imgUrl !== '') ? imgUrl : prodImg
|
||
if (img != null && img !== '') return img
|
||
}
|
||
return '/static/images/default.png'
|
||
},
|
||
|
||
getOrderTitle(order: OrderItemType): string {
|
||
const itemsRaw = order.ml_order_items
|
||
if (itemsRaw == null) return '精选商品'
|
||
const items = itemsRaw as any[]
|
||
if (items.length > 0) {
|
||
const firstItem = items[0]
|
||
const itemStr = JSON.stringify(firstItem)
|
||
const itemParsed = JSON.parse(itemStr)
|
||
if (itemParsed == null) return '精选商品'
|
||
const itemObj = itemParsed as UTSJSONObject
|
||
const pName = itemObj.getString('product_name')
|
||
const name = (pName != null && pName !== '') ? pName : '商品'
|
||
|
||
return name
|
||
}
|
||
return '精选商品'
|
||
},
|
||
|
||
getOrderSpec(order: OrderItemType): string {
|
||
const itemsRaw = order.ml_order_items
|
||
if (itemsRaw == null) return ''
|
||
const items = itemsRaw as any[]
|
||
if (items.length > 0) {
|
||
const firstItem = items[0]
|
||
const itemStr = JSON.stringify(firstItem)
|
||
const itemParsed = JSON.parse(itemStr)
|
||
if (itemParsed == null) return ''
|
||
const itemObj = itemParsed as UTSJSONObject
|
||
const specRaw = itemObj.get('specifications')
|
||
if (specRaw == null) return ''
|
||
|
||
if (typeof specRaw === 'string') {
|
||
const specStr = specRaw as string
|
||
if (specStr.startsWith('{')) {
|
||
try {
|
||
const specObj = JSON.parse(specStr) as UTSJSONObject
|
||
const parts: string[] = []
|
||
const color = specObj.get('Color')
|
||
if (color != null) parts.push('颜色: ' + color)
|
||
const size = specObj.get('Size')
|
||
if (size != null) parts.push('尺码: ' + size)
|
||
|
||
if (parts.length > 0) return parts.join(' ')
|
||
return specStr.replace(/[{}"]/g, '')
|
||
} catch (e) {
|
||
return specStr
|
||
}
|
||
}
|
||
return specStr
|
||
}
|
||
return JSON.stringify(specRaw).replace(/[{}"]/g, '')
|
||
}
|
||
return ''
|
||
},
|
||
|
||
getOrderItemCount(order: OrderItemType): number {
|
||
if (order.items_count != null && order.items_count > 0) {
|
||
return order.items_count
|
||
}
|
||
return 1
|
||
},
|
||
|
||
formatDateTime(timeStr: string): string {
|
||
if (timeStr == null || timeStr === '') return ''
|
||
const date = new Date(timeStr)
|
||
const y = date.getFullYear()
|
||
const m = (date.getMonth() + 1).toString().padStart(2, '0')
|
||
const d = date.getDate().toString().padStart(2, '0')
|
||
const h = date.getHours().toString().padStart(2, '0')
|
||
const i = date.getMinutes().toString().padStart(2, '0')
|
||
return `${y}-${m}-${d} ${h}:${i}`
|
||
},
|
||
|
||
formatTime(timeStr: string): string {
|
||
const date = new Date(timeStr)
|
||
const now = new Date()
|
||
const diff = now.getTime() - date.getTime()
|
||
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
||
|
||
if (days === 0) {
|
||
return '今天'
|
||
} else if (days === 1) {
|
||
return '昨天'
|
||
} else {
|
||
return `${days}天前`
|
||
}
|
||
},
|
||
|
||
ensureUserLoggedIn(redirectUrl: string): boolean {
|
||
const userId = supabaseService.getCurrentUserId()
|
||
if (userId == null || userId === '') {
|
||
goToLogin(redirectUrl)
|
||
return false
|
||
}
|
||
return true
|
||
},
|
||
|
||
switchStatsPeriod(period: string) {
|
||
this.activeStatsPeriod = period
|
||
this.loadConsumptionStats()
|
||
},
|
||
|
||
editProfile() {
|
||
const userId = supabaseService.getCurrentUserId()
|
||
if (userId == null || userId === '') {
|
||
this.goProfileLogin()
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/user/profile'
|
||
})
|
||
},
|
||
|
||
goProfileLogin() {
|
||
goToLogin('/pages/main/profile')
|
||
},
|
||
|
||
// 跳转设置
|
||
goToSettings() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/settings')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/settings'
|
||
})
|
||
},
|
||
|
||
// 跳转钱包
|
||
goToWallet() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/wallet')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/wallet'
|
||
})
|
||
},
|
||
|
||
goToRecommendProduct(item: RecommendProductType) {
|
||
if (item.id == null || item.id === '') {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/product-detail?id=${item.id}`
|
||
})
|
||
},
|
||
|
||
goToOrders(type: string) {
|
||
if (!this.ensureUserLoggedIn(`/pages/mall/consumer/orders?type=${type}`)) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/orders?type=${type}`
|
||
})
|
||
},
|
||
|
||
viewOrderDetail(order: OrderItemType) {
|
||
if (!this.ensureUserLoggedIn(`/pages/mall/consumer/order-detail?orderId=${order.id}`)) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/order-detail?orderId=${order.id}`
|
||
})
|
||
},
|
||
|
||
goToProductFromOrder(order: OrderItemType) {
|
||
const itemsRaw = order.ml_order_items
|
||
if (itemsRaw == null) return
|
||
const items = itemsRaw as any[]
|
||
if (items.length > 0) {
|
||
const firstItem = items[0]
|
||
const itemStr = JSON.stringify(firstItem)
|
||
const itemParsed = JSON.parse(itemStr)
|
||
if (itemParsed == null) return
|
||
const itemObj = itemParsed as UTSJSONObject
|
||
const productId = itemObj.getString('product_id')
|
||
if (productId != null && productId !== '') {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/product-detail?id=${productId}`
|
||
})
|
||
}
|
||
}
|
||
},
|
||
|
||
payOrder(order: OrderItemType) {
|
||
const paymentAmount = order.actual_amount
|
||
const userId = supabaseService.getCurrentUserId()
|
||
if (userId == null || userId === '') {
|
||
goToLogin(`/pages/mall/consumer/payment?orderId=${order.id}&amount=${paymentAmount}`)
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/payment?orderId=${order.id}&amount=${paymentAmount}`
|
||
})
|
||
},
|
||
|
||
confirmReceive(order: OrderItemType) {
|
||
uni.showModal({
|
||
title: '确认收货',
|
||
content: '确认已收到商品吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
uni.showLoading({ title: '处理中...' })
|
||
supabaseService.confirmOrderReceived(order.id).then(() => {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '确认收货成功',
|
||
icon: 'success'
|
||
})
|
||
this.loadOrders()
|
||
}).catch(() => {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '操作失败',
|
||
icon: 'none'
|
||
})
|
||
})
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
reviewOrder(order: OrderItemType) {
|
||
if (!this.ensureUserLoggedIn(`/pages/mall/consumer/review?orderId=${order.id}`)) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/review?orderId=${order.id}`
|
||
})
|
||
},
|
||
|
||
goToCoupons() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/coupons')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/coupons'
|
||
})
|
||
},
|
||
|
||
goToMessages() {
|
||
if (!this.ensureUserLoggedIn('/pages/main/messages')) {
|
||
return
|
||
}
|
||
uni.switchTab({
|
||
url: '/pages/main/messages'
|
||
})
|
||
},
|
||
|
||
goToPoints() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/points/index')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/points/index'
|
||
})
|
||
},
|
||
|
||
goToAddress() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/address-list')) {
|
||
return
|
||
}
|
||
// 暂时跳转到设置页的地址管理
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/address-list'
|
||
})
|
||
},
|
||
|
||
goToFavorites() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/favorites')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/favorites'
|
||
})
|
||
},
|
||
|
||
goToFootprint() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/footprint')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/footprint'
|
||
})
|
||
},
|
||
|
||
goToRefund() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/orders?type=refund')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/orders?type=refund'
|
||
})
|
||
},
|
||
|
||
contactService() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/service/chat')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/service/chat'
|
||
})
|
||
},
|
||
goToOrderReviews() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/orders?type=review')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/orders?type=review'
|
||
})
|
||
},
|
||
goToMySubscriptions() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/subscription/my-subscriptions')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/subscription/my-subscriptions'
|
||
})
|
||
},
|
||
goToFollowedShops() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/subscription/followed-shops')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/subscription/followed-shops'
|
||
})
|
||
},
|
||
goToBalance() {
|
||
if (!this.ensureUserLoggedIn('/pages/mall/consumer/balance/index')) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/balance/index'
|
||
})
|
||
},
|
||
|
||
goToShare() {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/share/index'
|
||
})
|
||
},
|
||
|
||
goToMember() {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/member/index'
|
||
})
|
||
},
|
||
|
||
changePassword() {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/change-password'
|
||
})
|
||
},
|
||
|
||
bindPhone() {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/bind-phone'
|
||
})
|
||
},
|
||
|
||
bindEmail() {
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/bind-email'
|
||
})
|
||
},
|
||
|
||
handleOrderUpdated(data: any) {
|
||
console.log('收到订单更新事件:', data)
|
||
this.refreshData()
|
||
|
||
const dataObj = data as UTSJSONObject
|
||
const status = dataObj.getNumber('status')
|
||
if (status === 1) {
|
||
uni.showToast({
|
||
title: '订单已保存到待支付',
|
||
icon: 'success'
|
||
})
|
||
} else if (status === 2) {
|
||
uni.showToast({
|
||
title: '支付成功,订单待发货',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
},
|
||
|
||
handleAuthChanged(data: any) {
|
||
this.refreshLoginState()
|
||
let payload: UTSJSONObject | null = null
|
||
|
||
if (data instanceof UTSJSONObject) {
|
||
payload = data as UTSJSONObject
|
||
} else {
|
||
try {
|
||
const raw = JSON.stringify(data)
|
||
if (raw !== '' && raw !== 'null') {
|
||
payload = JSON.parse(raw) as UTSJSONObject
|
||
}
|
||
} catch (e) {
|
||
console.error('[profile] authChanged payload parse failed:', e)
|
||
}
|
||
}
|
||
|
||
if (payload == null) {
|
||
return
|
||
}
|
||
|
||
const loggedIn = payload.getBoolean('loggedIn')
|
||
if (loggedIn === true) {
|
||
this.refreshData()
|
||
return
|
||
}
|
||
|
||
if (loggedIn === false) {
|
||
this.resetGuestProfileState()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.consumer-profile {
|
||
width: 100%;
|
||
height: 100vh;
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.profile-scroll-content {
|
||
flex: 1;
|
||
height: 0;
|
||
width: 100%;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.profile-page-inner {
|
||
width: 100%;
|
||
min-height: 100%;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.jd-profile-top {
|
||
position: relative;
|
||
width: 100%;
|
||
min-height: 260px;
|
||
padding-left: 14px;
|
||
padding-right: 14px;
|
||
padding-bottom: 24px;
|
||
box-sizing: border-box;
|
||
background-color: #ff5a36;
|
||
background-image: linear-gradient(180deg, #ff3b30 0%, #ff5a36 60%, #f5f5f5 100%);
|
||
}
|
||
|
||
.jd-profile-top-android {
|
||
background-image: none;
|
||
background-color: #ff5a36;
|
||
}
|
||
|
||
.jd-user-header {
|
||
min-height: 88px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.jd-user-brief {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.jd-user-avatar {
|
||
width: 58px;
|
||
height: 58px;
|
||
border-radius: 29px;
|
||
border-width: 2px;
|
||
border-style: solid;
|
||
border-color: rgba(255, 255, 255, 0.9);
|
||
background-color: #eeeeee;
|
||
flex-shrink: 0;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.jd-user-texts {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
}
|
||
|
||
.jd-user-name-row {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.jd-user-name {
|
||
flex: 1;
|
||
min-width: 0;
|
||
max-width: none;
|
||
font-size: 20px;
|
||
color: #ffffff;
|
||
font-weight: 800;
|
||
lines: 1;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.jd-user-tag-row {
|
||
margin-top: 6px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-wrap: nowrap;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.jd-user-tag {
|
||
height: 22px;
|
||
line-height: 22px;
|
||
padding-left: 8px;
|
||
padding-right: 8px;
|
||
margin-right: 6px;
|
||
border-radius: 11px;
|
||
background-color: rgba(255, 255, 255, 0.22);
|
||
color: rgba(255, 255, 255, 0.95);
|
||
font-size: 11px;
|
||
white-space: nowrap;
|
||
lines: 1;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.jd-user-tag-secondary {
|
||
flex: 1;
|
||
min-width: 0;
|
||
padding-left: 6px;
|
||
padding-right: 6px;
|
||
margin-right: 0;
|
||
font-size: 10px;
|
||
}
|
||
|
||
.capsule-below-actions {
|
||
position: absolute;
|
||
z-index: 20;
|
||
height: 48px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.capsule-action-item {
|
||
width: 48px;
|
||
height: 46px;
|
||
margin-left: 4px;
|
||
border-radius: 10px;
|
||
background-color: transparent;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.capsule-action-icon {
|
||
width: 20px;
|
||
height: 20px;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.capsule-action-fallback {
|
||
width: 20px;
|
||
height: 20px;
|
||
font-size: 13px;
|
||
color: #ffffff;
|
||
text-align: center;
|
||
line-height: 20px;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.capsule-action-text {
|
||
font-size: 11px;
|
||
color: #ffffff;
|
||
font-weight: 500;
|
||
line-height: 14px;
|
||
}
|
||
|
||
.jd-wallet-card {
|
||
position: relative;
|
||
margin-top: 2px;
|
||
height: 150px;
|
||
border-radius: 18px;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.wallet-plus-ribbon {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 68%;
|
||
height: 48px;
|
||
border-top-right-radius: 18px;
|
||
border-bottom-left-radius: 34px;
|
||
background-color: #8a6638;
|
||
background-image: linear-gradient(90deg, #5a3a21 0%, #75542f 52%, #a68049 100%);
|
||
z-index: 2;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.wallet-plus-ribbon-android {
|
||
background-image: none;
|
||
background-color: #8a6638;
|
||
}
|
||
|
||
|
||
|
||
.wallet-plus-bg-decor {
|
||
position: absolute;
|
||
right: -18px;
|
||
top: -24px;
|
||
width: 72px;
|
||
height: 72px;
|
||
border-radius: 36px;
|
||
background-color: rgba(255, 230, 180, 0.22);
|
||
}
|
||
|
||
.wallet-title-block {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 42%;
|
||
height: 56px;
|
||
padding-left: 16px;
|
||
border-top-left-radius: 18px;
|
||
border-bottom-right-radius: 42px;
|
||
background-color: #ffffff;
|
||
z-index: 4;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.wallet-curve-cover {
|
||
position: absolute;
|
||
left: 25%;
|
||
top: -24px;
|
||
width: 82px;
|
||
height: 94px;
|
||
border-radius: 47px;
|
||
background-color: #ffffff;
|
||
z-index: 5;
|
||
}
|
||
|
||
.wallet-plus-content {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 49%;
|
||
right: 8px;
|
||
height: 48px;
|
||
z-index: 6;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.wallet-crown-icon {
|
||
width: 18px;
|
||
font-size: 16px;
|
||
color: #ffd77a;
|
||
margin-right: 4px;
|
||
text-align: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.wallet-plus-title {
|
||
font-size: 13px;
|
||
color: #fff1d0;
|
||
font-weight: 800;
|
||
margin-right: 4px;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.wallet-plus-desc {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: 10px;
|
||
color: rgba(255, 241, 208, 0.86);
|
||
lines: 1;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.wallet-plus-arrow {
|
||
width: 22px;
|
||
height: 22px;
|
||
border-radius: 11px;
|
||
background-color: rgba(255, 240, 210, 0.88);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-left: 5px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.wallet-plus-arrow-text {
|
||
font-size: 18px;
|
||
color: #7a5730;
|
||
line-height: 22px;
|
||
}
|
||
|
||
.wallet-title-text {
|
||
font-size: 17px;
|
||
color: #222222;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.wallet-title-arrow {
|
||
margin-left: 5px;
|
||
font-size: 18px;
|
||
color: #222222;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.wallet-assets-row {
|
||
position: relative;
|
||
z-index: 6;
|
||
margin-top: 62px;
|
||
padding-left: 8px;
|
||
padding-right: 48px;
|
||
height: 70px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.wallet-asset-item {
|
||
flex: 1;
|
||
min-width: 0;
|
||
height: 64px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.wallet-asset-value {
|
||
font-size: 19px;
|
||
color: #111111;
|
||
font-weight: 800;
|
||
lines: 1;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.wallet-asset-label {
|
||
margin-top: 4px;
|
||
font-size: 12px;
|
||
color: #666666;
|
||
lines: 1;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.wallet-more-entry {
|
||
position: absolute;
|
||
right: 8px;
|
||
top: 64px;
|
||
width: 36px;
|
||
height: 70px;
|
||
border-left-width: 1px;
|
||
border-left-style: solid;
|
||
border-left-color: #eeeeee;
|
||
z-index: 7;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.wallet-more-text {
|
||
font-size: 12px;
|
||
color: #333333;
|
||
line-height: 16px;
|
||
text-align: center;
|
||
}
|
||
|
||
.wallet-more-circle {
|
||
margin-top: 5px;
|
||
width: 23px;
|
||
height: 23px;
|
||
border-radius: 12px;
|
||
border-width: 1px;
|
||
border-style: solid;
|
||
border-color: #666666;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.wallet-more-arrow {
|
||
font-size: 18px;
|
||
color: #333333;
|
||
line-height: 22px;
|
||
}
|
||
|
||
.jd-section-card {
|
||
margin: 12px 14px 0 14px;
|
||
padding: 16px;
|
||
border-radius: 16px;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.order-card {
|
||
margin: 12px 14px 0 14px;
|
||
padding: 14px 16px 14px;
|
||
border-radius: 18px;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
color: #222222;
|
||
}
|
||
|
||
.card-subtitle {
|
||
margin-top: 6px;
|
||
display: block;
|
||
font-size: 12px;
|
||
color: #8b90a0;
|
||
}
|
||
|
||
.card-link {
|
||
font-size: 13px;
|
||
color: #999999;
|
||
}
|
||
|
||
.order-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.order-title {
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
color: #222222;
|
||
}
|
||
|
||
.order-all-link {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
color: #999999;
|
||
}
|
||
|
||
.order-all-arrow {
|
||
margin-left: 4px;
|
||
font-size: 15px;
|
||
color: #bbbbbb;
|
||
line-height: 15px;
|
||
}
|
||
|
||
.order-actions {
|
||
margin-top: 14px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
overflow: visible;
|
||
}
|
||
|
||
.order-action {
|
||
width: 25%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
overflow: visible;
|
||
}
|
||
|
||
.order-action.active .order-action-text {
|
||
color: #222222;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.order-icon-wrap {
|
||
position: relative;
|
||
width: 40px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: transparent;
|
||
border-radius: 0;
|
||
overflow: visible;
|
||
}
|
||
|
||
.order-icon-img {
|
||
width: 32px;
|
||
height: 32px;
|
||
display: block;
|
||
}
|
||
|
||
.order-badge {
|
||
position: absolute;
|
||
top: -3px;
|
||
right: -5px;
|
||
z-index: 5;
|
||
min-width: 14px;
|
||
height: 14px;
|
||
padding-left: 3px;
|
||
padding-right: 3px;
|
||
border-radius: 999px;
|
||
background-color: #ff3b30;
|
||
border-width: 1px;
|
||
border-style: solid;
|
||
border-color: #ffffff;
|
||
color: #ffffff;
|
||
font-size: 9px;
|
||
font-weight: 600;
|
||
line-height: 14px;
|
||
text-align: center;
|
||
}
|
||
|
||
.order-action-text {
|
||
margin-top: 8px;
|
||
font-size: 12px;
|
||
color: #444444;
|
||
line-height: 16px;
|
||
text-align: center;
|
||
}
|
||
|
||
.pending-receipt-strip {
|
||
margin-top: 14px;
|
||
min-height: 50px;
|
||
padding: 8px 10px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
background-color: #f7f8fa;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.pending-goods-img {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 4px;
|
||
flex-shrink: 0;
|
||
background-color: #eeeeee;
|
||
}
|
||
|
||
.pending-goods-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.pending-goods-title-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
min-width: 0;
|
||
}
|
||
|
||
.pending-status {
|
||
margin-right: 8px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #333333;
|
||
}
|
||
|
||
.pending-desc {
|
||
flex: 1;
|
||
font-size: 11px;
|
||
color: #888888;
|
||
lines: 1;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.pending-goods-name {
|
||
margin-top: 3px;
|
||
display: block;
|
||
font-size: 11px;
|
||
color: #666666;
|
||
lines: 1;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.pending-arrow {
|
||
margin-left: 10px;
|
||
font-size: 18px;
|
||
color: #bbbbbb;
|
||
}
|
||
|
||
.service-card {
|
||
margin: 12px 14px 0 14px;
|
||
padding: 14px 0 14px;
|
||
border-radius: 18px;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.04);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.service-header {
|
||
padding-left: 16px;
|
||
padding-right: 16px;
|
||
margin-bottom: 14px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.service-title {
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
color: #222222;
|
||
}
|
||
|
||
.service-scroll {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.service-row {
|
||
display: inline-flex;
|
||
flex-direction: row;
|
||
flex-wrap: nowrap;
|
||
align-items: flex-start;
|
||
padding-left: 12px;
|
||
padding-right: 18px;
|
||
}
|
||
|
||
.service-item {
|
||
width: 64px;
|
||
flex: 0 0 64px;
|
||
flex-shrink: 0;
|
||
margin-right: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.service-icon-wrap {
|
||
width: 28px;
|
||
height: 28px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
background-color: transparent;
|
||
border-radius: 0;
|
||
overflow: visible;
|
||
}
|
||
|
||
.service-icon-img {
|
||
width: 26px;
|
||
height: 26px;
|
||
display: block;
|
||
}
|
||
|
||
.service-text-icon {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #999999;
|
||
line-height: 28px;
|
||
}
|
||
|
||
.service-badge {
|
||
min-width: 14px;
|
||
height: 14px;
|
||
padding-left: 3px;
|
||
padding-right: 3px;
|
||
border-radius: 7px;
|
||
background-color: #ff3d2b;
|
||
color: #ffffff;
|
||
font-size: 9px;
|
||
font-weight: 700;
|
||
text-align: center;
|
||
line-height: 14px;
|
||
position: absolute;
|
||
top: -6px;
|
||
right: -8px;
|
||
}
|
||
|
||
.service-name {
|
||
margin-top: 6px;
|
||
font-size: 11px;
|
||
color: #333333;
|
||
line-height: 14px;
|
||
text-align: center;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.service-row-end-spacer {
|
||
width: 18px;
|
||
height: 1px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.recommend-section {
|
||
margin: 16px 16px 0 16px;
|
||
}
|
||
|
||
.recommend-header {
|
||
padding-left: 4px;
|
||
padding-right: 4px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.recommend-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #222222;
|
||
}
|
||
|
||
.recommend-subtitle {
|
||
display: block;
|
||
margin-top: 6px;
|
||
font-size: 12px;
|
||
color: #8b90a0;
|
||
}
|
||
|
||
.recommend-grid {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.recommend-card {
|
||
width: 48.2%;
|
||
margin-bottom: 12px;
|
||
border-radius: 18px;
|
||
background-color: #ffffff;
|
||
overflow: hidden;
|
||
box-shadow: 0 8px 24px rgba(32, 35, 66, 0.07);
|
||
}
|
||
|
||
.recommend-image {
|
||
width: 100%;
|
||
height: 132px;
|
||
background-color: #f0f1f5;
|
||
}
|
||
|
||
.recommend-info {
|
||
padding: 12px;
|
||
}
|
||
|
||
.recommend-name {
|
||
font-size: 13px;
|
||
color: #222222;
|
||
line-height: 20px;
|
||
lines: 2;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.recommend-meta-row {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.recommend-price {
|
||
font-size: 16px;
|
||
color: #ff4d31;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.recommend-tag {
|
||
padding-left: 8px;
|
||
padding-right: 8px;
|
||
height: 22px;
|
||
border-radius: 11px;
|
||
background-color: #fff0eb;
|
||
color: #ff5a3a;
|
||
font-size: 11px;
|
||
line-height: 22px;
|
||
}
|
||
|
||
.recommend-load-more {
|
||
width: 100%;
|
||
height: 48px;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-direction: row;
|
||
justify-content: center;
|
||
}
|
||
|
||
.recommend-load-text {
|
||
font-size: 13px;
|
||
color: #999999;
|
||
}
|
||
|
||
.profile-bottom-safe {
|
||
height: 156px;
|
||
}
|
||
|
||
.guest-login-bar {
|
||
position: fixed;
|
||
left: 12px;
|
||
right: 12px;
|
||
bottom: calc(var(--window-bottom) + 12px);
|
||
height: 48px;
|
||
padding: 0 8px 0 10px;
|
||
border-radius: 24px;
|
||
background-color: rgba(45, 38, 42, 0.72);
|
||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
|
||
backdrop-filter: blur(10px);
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
z-index: 998;
|
||
}
|
||
|
||
.guest-login-left {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.guest-login-avatar {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 16px;
|
||
margin-right: 8px;
|
||
background-color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
|
||
.guest-login-text {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: 12px;
|
||
color: #ffffff;
|
||
lines: 1;
|
||
}
|
||
|
||
.guest-login-btn {
|
||
height: 32px;
|
||
line-height: 32px;
|
||
padding: 0 14px;
|
||
border-radius: 16px;
|
||
background-color: #ff6a4d;
|
||
color: #ffffff;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.guest-login-btn::after {
|
||
border: none;
|
||
}
|
||
|
||
.pending {
|
||
color: #ff4d31;
|
||
}
|
||
|
||
.processing {
|
||
color: #ff8b2b;
|
||
}
|
||
|
||
.shipping {
|
||
color: #3f7cff;
|
||
}
|
||
|
||
.completed {
|
||
color: #16a34a;
|
||
}
|
||
|
||
.refunding {
|
||
color: #ff8b2b;
|
||
}
|
||
|
||
.refunded,
|
||
.cancelled,
|
||
.error {
|
||
color: #9aa0af;
|
||
}
|
||
|
||
@media screen and (max-width: 375px) {
|
||
.capsule-below-actions {
|
||
width: 96px !important;
|
||
}
|
||
|
||
.jd-wallet-card {
|
||
height: 144px;
|
||
}
|
||
|
||
.wallet-plus-ribbon {
|
||
width: 67%;
|
||
}
|
||
|
||
.wallet-title-block {
|
||
width: 41%;
|
||
padding-left: 12px;
|
||
}
|
||
|
||
.wallet-curve-cover {
|
||
left: 36%;
|
||
top: -24px;
|
||
width: 76px;
|
||
height: 88px;
|
||
border-radius: 44px;
|
||
}
|
||
|
||
.wallet-plus-content {
|
||
left: 49%;
|
||
right: 8px;
|
||
}
|
||
|
||
.wallet-title-text {
|
||
font-size: 15px;
|
||
}
|
||
|
||
.wallet-crown-icon {
|
||
width: 16px;
|
||
font-size: 15px;
|
||
margin-right: 3px;
|
||
}
|
||
|
||
.wallet-plus-title {
|
||
font-size: 12px;
|
||
margin-right: 3px;
|
||
}
|
||
|
||
.wallet-plus-desc {
|
||
font-size: 9px;
|
||
}
|
||
|
||
.wallet-assets-row {
|
||
margin-top: 60px;
|
||
padding-left: 4px;
|
||
padding-right: 44px;
|
||
}
|
||
|
||
.wallet-asset-value {
|
||
font-size: 17px;
|
||
}
|
||
|
||
.wallet-asset-label {
|
||
font-size: 11px;
|
||
}
|
||
|
||
.capsule-action-item {
|
||
width: 46px;
|
||
height: 42px;
|
||
margin-left: 2px;
|
||
}
|
||
|
||
.jd-user-name {
|
||
font-size: 19px;
|
||
}
|
||
|
||
.jd-user-avatar {
|
||
width: 54px;
|
||
height: 54px;
|
||
border-radius: 27px;
|
||
}
|
||
|
||
.jd-user-tag {
|
||
font-size: 10px;
|
||
padding-left: 6px;
|
||
padding-right: 6px;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.jd-user-tag-secondary {
|
||
padding-left: 4px;
|
||
padding-right: 4px;
|
||
font-size: 9px;
|
||
}
|
||
}
|
||
</style>
|