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

1327 lines
35 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">
<!-- 智能顶部导航栏 - 与消息页保持一致 -->
<view class="smart-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-container">
<!-- 头像 -->
<image
:src="userInfo.avatar_url != '' ? userInfo.avatar_url : '/static/default-avatar.png'"
class="nav-avatar"
@click="editProfile"
/>
<!-- 用户信息横向排列 (名字、积分、余额、优惠券) -->
<view class="nav-user-stats">
<text class="nav-user-name">{{ userInfo.nickname != '' ? userInfo.nickname : userInfo.phone }}</text>
<view class="nav-stat-item" @click="goToPoints">
<text class="nav-stat-label">积分</text>
<text class="nav-stat-value">{{ userStats.points }}</text>
</view>
<view class="nav-stat-item">
<text class="nav-stat-label">余额</text>
<text class="nav-stat-value" @click="goToWallet">¥{{ userStats.balance }}</text>
</view>
<view class="nav-stat-item" @click="goToCoupons">
<text class="nav-stat-label">券</text>
<text class="nav-stat-value">{{ serviceCounts.coupons }}</text>
</view>
</view>
<!-- 设置按钮 (右侧) -->
<view class="nav-actions">
<view class="action-btn" @click="goToSettings">
<text class="action-icon">⚙️</text>
</view>
</view>
</view>
</view>
<scroll-view class="profile-scroll-content" :scroll-y="true">
<!-- 导航栏占位符 - 需要包含statusBarHeight + 导航栏高度44px -->
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 我的服务 (移到订单上方) -->
<view class="my-services" style="margin-top: 10px;">
<view class="section-title">我的服务</view>
<view class="service-grid">
<view class="service-item" @click="goToCoupons">
<text class="service-icon">🎫</text>
<text class="service-text">优惠券</text>
<text v-if="serviceCounts.coupons > 0" class="service-badge">{{ serviceCounts.coupons }}</text>
</view>
<view class="service-item" @click="goToAddress">
<text class="service-icon">📍</text>
<text class="service-text">收货地址</text>
</view>
<view class="service-item" @click="goToFavorites">
<text class="service-icon">❤️</text>
<text class="service-text">我的收藏</text>
<text v-if="serviceCounts.favorites > 0" class="service-badge">{{ serviceCounts.favorites }}</text>
</view>
<view class="service-item" @click="goToFootprint">
<text class="service-icon">👣</text>
<text class="service-text">浏览足迹</text>
</view>
<view class="service-item" @click="goToRefund">
<text class="service-icon">🔄</text>
<text class="service-text">退款/售后</text>
</view>
<view class="service-item" @click="goToOrderReviews">
<text class="service-icon">📝</text>
<text class="service-text">评价</text>
</view>
<view class="service-item" @click="goToFollowedShops">
<text class="service-icon">⭐</text>
<text class="service-text">关注店铺</text>
</view>
<view class="service-item" @click="goToSubscriptions">
<text class="service-icon">📱</text>
<text class="service-text">软件订阅</text>
</view>
</view>
</view>
<!-- 订单状态快捷入口 -->
<view class="order-shortcuts">
<view class="section-title">我的订单</view>
<view class="order-tabs">
<view class="order-tab" :class="{ active: currentOrderTab === 'all' }" @click="switchOrderTab('all')">
<text class="tab-icon">📋</text>
<text class="tab-text">全部</text>
<text v-if="orderCounts.total > 0" class="tab-badge">{{ orderCounts.total }}</text>
</view>
<view class="order-tab" :class="{ active: currentOrderTab === 'pending' }" @click="switchOrderTab('pending')">
<text class="tab-icon">💰</text>
<text class="tab-text">待支付</text>
<text v-if="orderCounts.pending > 0" class="tab-badge">{{ orderCounts.pending }}</text>
</view>
<view class="order-tab" :class="{ active: currentOrderTab === 'toship' }" @click="switchOrderTab('toship')">
<text class="tab-icon">🚚</text>
<text class="tab-text">待发货</text>
<text v-if="orderCounts.toship > 0" class="tab-badge">{{ orderCounts.toship }}</text>
</view>
<view class="order-tab" :class="{ active: currentOrderTab === 'shipped' }" @click="switchOrderTab('shipped')">
<text class="tab-icon">📦</text>
<text class="tab-text">待收货</text>
<text v-if="orderCounts.shipped > 0" class="tab-badge">{{ orderCounts.shipped }}</text>
</view>
</view>
</view>
<!-- 最近订单列表 (根据Tab切换显示) -->
<view class="recent-orders">
<view class="section-header">
<text class="section-title">{{ getOrderSectionTitle() }}</text>
<text class="view-all" @click="goToOrders(currentOrderTab)">查看更多 ></text>
</view>
<view v-if="filteredOrders.length === 0" class="empty-orders">
<text class="empty-text">暂无相关订单记录</text>
<button class="start-shopping" @click="goShopping">去逛逛</button>
</view>
<view v-for="order in filteredOrders" :key="order.id" class="order-item" @click="viewOrderDetail(order)">
<view class="order-header">
<text class="order-no">订单号: {{ order.order_no }}</text>
<text class="order-status" :class="getOrderStatusClass(order.status)">{{ getOrderStatusText(order.status) }}</text>
</view>
<view class="order-content">
<image :src="getOrderMainImage(order)" class="order-image" mode="aspectFill" />
<view class="order-info">
<text class="order-title">{{ getOrderTitle(order) }}</text>
<text class="order-amount">¥{{ order.actual_amount }}</text>
<text class="order-time">{{ formatTime(order.created_at) }}</text>
</view>
</view>
<view class="order-actions">
<button v-if="order.status === 1" class="action-btn pay" @click.stop="payOrder(order)">立即支付</button>
<button v-if="order.status === 3" class="action-btn confirm" @click.stop="confirmReceive(order)">确认收货</button>
<button v-if="order.status === 4" class="action-btn review" @click.stop="reviewOrder(order)">评价</button>
</view>
</view>
</view>
<!-- 我的服务 -->
<!-- <view class="my-services">
<view class="section-title">我的服务</view>
<view class="service-grid">
<view class="service-item" @click="goToCoupons">
<text class="service-icon">🎫</text>
<text class="service-text">优惠券</text>
<text v-if="serviceCounts.coupons > 0" class="service-badge">{{ serviceCounts.coupons }}</text>
</view>
<view class="service-item" @click="goToAddress">
<text class="service-icon">📍</text>
<text class="service-text">收货地址</text>
</view>
<view class="service-item" @click="goToFavorites">
<text class="service-icon">❤️</text>
<text class="service-text">我的收藏</text>
<text v-if="serviceCounts.favorites > 0" class="service-badge">{{ serviceCounts.favorites }}</text>
</view>
<view class="service-item" @click="goToFootprint">
<text class="service-icon">👣</text>
<text class="service-text">浏览足迹</text>
</view>
<view class="service-item" @click="goToRefund">
<text class="service-icon">🔄</text>
<text class="service-text">退款/售后</text>
</view>
<view class="service-item" @click="contactService">
<text class="service-icon">💬</text>
<text class="service-text">在线客服</text>
</view>
<view class="service-item" @click="goToMySubscriptions">
<text class="service-icon">🧩</text>
<text class="service-text">我的订阅</text>
</view>
<view class="service-item" @click="goToSubscriptions">
<text class="service-icon">🧩</text>
<text class="service-text">软件订阅</text>
</view>
</view>
</view> -->
<!-- 消费统计 -->
<view class="consumption-stats">
<view class="section-title">消费统计</view>
<view class="stats-period">
<text v-for="period in statsPeriods" :key="period.key"
class="period-tab"
:class="{ active: activeStatsPeriod === period.key }"
@click="switchStatsPeriod(period.key)">{{ period.label }}</text>
</view>
<view class="stats-content">
<view class="stat-card">
<text class="stat-value">¥{{ currentStats.total_amount }}</text>
<text class="stat-label">总消费</text>
</view>
<view class="stat-card">
<text class="stat-value">{{ currentStats.order_count }}</text>
<text class="stat-label">订单数</text>
</view>
<view class="stat-card">
<text class="stat-value">¥{{ currentStats.avg_amount }}</text>
<text class="stat-label">平均消费</text>
</view>
<view class="stat-card">
<text class="stat-value">{{ currentStats.save_amount }}</text>
<text class="stat-label">节省金额</text>
</view>
</view>
</view>
<!-- 账户安全 -->
<!-- <view class="account-security">
<view class="section-title">账户安全</view>
<view class="security-items">
<view class="security-item" @click="changePassword">
<text class="security-icon">🔒</text>
<text class="security-text">修改密码</text>
<text class="security-arrow">></text>
</view>
<view class="security-item" @click="bindPhone">
<text class="security-icon">📱</text>
<text class="security-text">手机绑定</text>
<view class="security-status">
<text class="status-text" :class="{ bound: userInfo.phone }">{{ userInfo.phone ? '已绑定' : '未绑定' }}</text>
<text class="security-arrow">></text>
</view>
</view>
<view class="security-item" @click="bindEmail">
<text class="security-icon">📧</text>
<text class="security-text">邮箱绑定</text>
<view class="security-status">
<text class="status-text" :class="{ bound: userInfo.email }">{{ userInfo.email ? '已绑定' : '未绑定' }}</text>
<text class="security-arrow">></text>
</view>
</view>
</view>
</view> -->
</scroll-view>
</view>
</template>
<script>
import { UserType } from '@/types/mall-types.uts'
import supabaseService from '@/utils/supabaseService.uts'
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
}
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
}
export default {
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: 1
} as UserStatsType,
orderCounts: {
total: 0,
pending: 0,
toship: 0,
shipped: 0,
review: 0
} as OrderCountsType,
serviceCounts: {
coupons: 0,
favorites: 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,
statusBarHeight: 0,
currentOrderTab: 'all' as string,
allOrders: [] as Array<OrderItemType>
}
},
onLoad() {
this.initPage()
this.loadUserProfile()
this.loadOrders()
// 监听订单更新事件
uni.$on('orderUpdated', this.handleOrderUpdated)
},
onShow() {
this.refreshData()
},
onUnload() {
// 移除事件监听
uni.$off('orderUpdated', this.handleOrderUpdated)
},
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
}
},
methods: {
async loadOrders() {
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 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: o.get('ml_order_items')
}
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
}
} catch (e) {
console.error('加载订单异常', e)
}
},
// 切换订单Tab
switchOrderTab(tab: string) {
this.currentOrderTab = tab
},
// 获取当前订单部分标题
getOrderSectionTitle(): string {
if (this.currentOrderTab === 'all') return '全部订单'
if (this.currentOrderTab === 'pending') return '待支付订单'
if (this.currentOrderTab === 'shipped') return '待收货订单'
if (this.currentOrderTab === 'review') return '待评价订单'
return '我的订单'
},
initPage() {
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight ?? 0
},
async loadUserProfile() {
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)
}
this.userInfo = {
id: uId,
phone: uPhone,
email: uEmail,
nickname: uNickname != '' ? uNickname : '微信用户',
avatar_url: uAvatar != '' ? uAvatar : '/static/default-avatar.png',
gender: uGender,
user_type: 1,
status: 1,
created_at: new Date().toISOString()
} as UserType
} else {
// 如果获取失败未登录或无档案尝试获取当前登录ID
const userId = supabaseService.getCurrentUserId()
if (userId != null) {
this.userInfo.id = userId
this.userInfo.nickname = '用户' + userId.substring(0, 4)
} else {
this.userInfo.nickname = '未登录'
}
}
// 获取积分和余额(并行获取)
const [balance, points] = await Promise.all([
supabaseService.getUserBalance(),
supabaseService.getUserPoints()
])
this.userStats = {
points: points,
balance: balance,
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.loadUserProfile()
this.loadOrders()
this.updateCouponCount() // 更新优惠券数量
},
async updateCouponCount() {
// 从 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 {
const statusTexts = ['异常', '待支付', '待发货', '待收货', '已完成', '已取消']
if (status >= 0 && status < statusTexts.length) {
return statusTexts[status]
}
return '未知'
},
getOrderStatusClass(status: number): string {
const statusClasses = ['error', 'pending', 'processing', 'shipping', 'completed', 'cancelled']
if (status >= 0 && status < statusClasses.length) {
return statusClasses[status]
}
return 'error'
},
getOrderMainImage(order: OrderItemType): string {
const itemsRaw = order.ml_order_items
if (itemsRaw == null) return '/static/product1.jpg'
const items = itemsRaw as any[]
if (items.length > 0) {
const firstItem = items[0] as Record<string, any>
const imgUrl = firstItem['image_url'] as string
const prodImg = firstItem['product_image'] as string
const img = (imgUrl != null && imgUrl !== '') ? imgUrl : prodImg
if (img != null && img !== '') return img
}
return '/static/product1.jpg'
},
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] as Record<string, any>
const pName = firstItem['product_name'] as string
const name = (pName != null && pName !== '') ? pName : '商品'
if (items.length > 1) {
return `${name} 等${items.length}件商品`
}
return name
}
return '精选商品'
},
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}天前`
}
},
switchStatsPeriod(period: string) {
this.activeStatsPeriod = period
this.loadConsumptionStats()
},
editProfile() {
uni.navigateTo({
url: '/pages/mall/consumer/edit-profile'
})
},
// 跳转设置
goToSettings() {
uni.navigateTo({
url: '/pages/mall/consumer/settings'
})
},
// 跳转钱包
goToWallet() {
uni.navigateTo({
url: '/pages/mall/consumer/wallet'
})
},
goToOrders(type: string) {
uni.navigateTo({
url: `/pages/mall/consumer/orders?type=${type}`
})
},
goShopping() {
uni.switchTab({
url: '/pages/mall/consumer/index'
})
},
viewOrderDetail(order: OrderItemType) {
uni.navigateTo({
url: `/pages/mall/consumer/order-detail?orderId=${order.id}`
})
},
payOrder(order: OrderItemType) {
uni.navigateTo({
url: `/pages/mall/consumer/payment?orderId=${order.id}`
})
},
confirmReceive(order: OrderItemType) {
uni.showModal({
title: '确认收货',
content: '确认已收到商品吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '确认收货成功',
icon: 'success'
})
this.refreshData()
}
}
})
},
reviewOrder(order: OrderItemType) {
uni.navigateTo({
url: `/pages/mall/consumer/review?orderId=${order.id}`
})
},
goToCoupons() {
uni.navigateTo({
url: '/pages/mall/consumer/coupons'
})
},
goToPoints() {
uni.navigateTo({
url: '/pages/mall/consumer/points/index'
})
},
goToAddress() {
// 暂时跳转到设置页的地址管理
uni.navigateTo({
url: '/pages/mall/consumer/address-list'
})
},
goToFavorites() {
uni.navigateTo({
url: '/pages/mall/consumer/favorites'
})
},
goToFootprint() {
uni.navigateTo({
url: '/pages/mall/consumer/footprint'
})
},
goToRefund() {
uni.navigateTo({
url: '/pages/mall/consumer/orders?type=refund'
})
},
contactService() {
uni.navigateTo({
url: '/pages/mall/service/chat'
})
},
goToOrderReviews() {
uni.navigateTo({
url: '/pages/mall/consumer/orders?type=review'
})
},
goToMySubscriptions() {
uni.navigateTo({
url: '/pages/mall/consumer/subscription/my-subscriptions'
})
},
goToFollowedShops() {
uni.navigateTo({
url: '/pages/mall/consumer/subscription/followed-shops'
})
},
goToSubscriptions() {
uni.navigateTo({
url: '/pages/mall/consumer/subscription/plan-list'
})
},
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'
})
}
}
}
}
</script>
<style>
.consumer-profile {
background-color: #f5f5f5;
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
.profile-scroll-content {
flex: 1;
}
/* 智能顶部导航栏 */
.smart-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
background-color: #4CAF50;
z-index: 1000;
box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.nav-container {
padding: 0 16px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 1400px;
margin: 0 auto;
height: 44px;
}
/* 导航栏用户信息区域 */
.nav-user-stats {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start; /* 靠左对齐,紧跟头像 */
margin-right: 12px;
overflow: hidden; /* 防止溢出 */
}
.nav-user-name {
font-size: 16px;
font-weight: bold;
color: white;
margin-right: 12px;
/* max-width: 30%; REMOVED */
width: 100px; /* Use fixed width approx */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nav-stat-item {
display: flex;
flex-direction: row;
align-items: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 2px 8px;
margin-right: 8px;
flex-shrink: 0; /* 防止被压缩 */
}
.nav-stat-label {
font-size: 11px;
color: rgba(255, 255, 255, 0.9);
margin-right: 4px;
}
.nav-stat-value {
font-size: 12px;
font-weight: bold;
color: white;
}
.nav-avatar {
width: 36px;
height: 36px;
border-radius: 18px;
border: 2px solid rgba(255, 255, 255, 0.8);
margin-right: 12px;
flex-shrink: 0;
}
.nav-actions {
display: flex;
flex-direction: row;
align-items: center;
flex-shrink: 0;
}
.action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: rgba(255, 255, 255, 0.2);
border-radius: 16px;
/* cursor: pointer; REMOVED */
}
.action-icon {
font-size: 18px;
color: white;
}
/* 导航栏占位符 */
.navbar-placeholder {
width: 100%;
flex-shrink: 0;
}
.order-shortcuts, .recent-orders, .my-services, .consumption-stats, .account-security {
background-color: #fff;
margin: 15px 15px; /* 顶部恢复 margin */
border-radius: 12px; /* 统一圆角 */
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 16px;
}
.order-tabs {
display: flex;
flex-direction: row; /* 显式横向排列 */
justify-content: space-between;
width: 100%;
}
.order-tab {
flex: 1;
display: flex;
flex-direction: row; /* 关键:改为横向排列 */
align-items: center;
justify-content: center; /* 居中 */
position: relative;
padding: 8px 0;
}
.tab-icon {
font-size: 20px;
margin-right: 6px; /* 图标和文字间距 */
margin-bottom: 0; /* 移除底部间距 */
}
.tab-text {
font-size: 14px;
color: #333;
}
/* 选中状态的Tab */
.order-tab.active .tab-icon,
.order-tab.active .tab-text {
color: #4CAF50;
font-weight: bold;
}
.order-tab.active {
background-color: #f0f9f0;
border-radius: 8px;
}
.tab-badge {
position: absolute;
top: 0;
right: 10%; /* 调整位置 */
background-color: #ff4444;
color: #fff;
font-size: 10px;
padding: 1px 5px;
border-radius: 8px;
min-width: 14px;
text-align: center;
line-height: 1.2;
}
.empty-orders {
text-align: center;
padding: 80rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-bottom: 30rpx;
}
.start-shopping {
background-color: #007aff;
color: #fff;
padding: 20rpx 40rpx;
border-radius: 25rpx;
font-size: 26rpx;
border: none;
}
.order-item {
padding: 25rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.order-item:last-child {
border-bottom: none;
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
}
.order-no {
font-size: 26rpx;
color: #333;
}
.order-status {
font-size: 24rpx;
padding: 6rpx 12rpx;
border-radius: 10rpx;
color: #fff;
}
.order-status.pending {
background-color: #ffa726;
}
.order-status.processing {
background-color: #2196f3;
}
.order-status.shipping {
background-color: #9c27b0;
}
.order-status.completed {
background-color: #4caf50;
}
.order-content {
display: flex;
align-items: center;
margin-bottom: 15rpx;
}
.order-image {
width: 100rpx;
height: 100rpx;
border-radius: 8rpx;
margin-right: 20rpx;
}
.order-info {
flex: 1;
}
.order-title {
font-size: 26rpx;
color: #333;
margin-bottom: 8rpx;
}
.order-amount {
font-size: 28rpx;
color: #ff4444;
font-weight: bold;
margin-bottom: 5rpx;
}
.order-time {
font-size: 22rpx;
color: #999;
}
.order-actions {
display: flex;
justify-content: flex-end;
/* gap: 15rpx; REMOVED */
}
.order-actions .action-btn {
margin-left: 15px; /* Replace gap */
}
.action-btn {
padding: 12rpx 25rpx;
border-radius: 20rpx;
font-size: 24rpx;
border: none;
}
.action-btn.pay {
background-color: #ff4444;
color: #fff;
}
.action-btn.confirm {
background-color: #4caf50;
color: #fff;
}
.action-btn.review {
background-color: #ffa726;
color: #fff;
}
.service-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap; /* 允许换行 */
/* gap: 16px 0; REMOVED */
justify-content: flex-start; /* 从左开始排列 */
}
.service-item {
width: 25%; /* 每行4个 */
display: flex;
flex-direction: column;
align-items: center;
position: relative;
box-sizing: border-box; /* 确保 padding 不影响宽度 */
margin-bottom: 16px; /* Replace gap row */
}
.service-icon {
font-size: 48rpx;
margin-bottom: 15rpx;
}
.service-text {
font-size: 24rpx;
color: #333;
}
.service-badge {
position: absolute;
top: -5rpx;
right: 10rpx;
background-color: #ff4444;
color: #fff;
font-size: 18rpx;
padding: 4rpx 6rpx;
border-radius: 8rpx;
min-width: 24rpx;
text-align: center;
}
.stats-period {
display: flex;
/* gap: 30rpx; REMOVED */
margin-bottom: 30rpx;
}
.period-tab {
font-size: 26rpx;
color: #666;
padding: 12rpx 24rpx;
border-radius: 20rpx;
margin-right: 30rpx; /* Replace gap */
background-color: #f0f0f0;
}
.period-tab.active {
background-color: #007aff;
color: #fff;
}
.stats-content {
display: flex;
/* gap: 20rpx; REMOVED */
}
.stat-card {
flex: 1;
text-align: center;
padding: 30rpx 0;
background-color: #f8f9fa;
border-radius: 10rpx;
margin-right: 20rpx; /* Replace gap */
}
.stat-card:last-child {
margin-right: 0;
}
.stat-value {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 22rpx;
color: #666;
}
.security-items {
margin-top: 25rpx;
}
.security-item {
display: flex;
align-items: center;
padding: 25rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.security-item:last-child {
border-bottom: none;
}
.security-icon {
font-size: 32rpx;
margin-right: 20rpx;
}
.security-text {
flex: 1;
font-size: 28rpx;
color: #333;
}
.security-status {
display: flex;
align-items: center;
}
.status-text {
font-size: 24rpx;
color: #999;
margin-right: 10rpx;
}
.status-text.bound {
color: #4caf50;
}
.security-arrow {
font-size: 24rpx;
color: #999;
}
</style>