Files
medical-mall/pages/main/profile.uvue

2618 lines
70 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 消费者端 - 个人中心 -->
<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>
</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,
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.loadUserProfile()
this.loadOrders()
// 监听订单更新事件
uni.$on('orderUpdated', this.handleOrderUpdated)
uni.$on('authChanged', this.handleAuthChanged)
},
onShow() {
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: {
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() {
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() {
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 === '') {
goToLogin('/pages/user/profile')
return
}
uni.navigateTo({
url: '/pages/user/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) {
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 === 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: 90px;
}
.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>