1337 lines
37 KiB
Plaintext
1337 lines
37 KiB
Plaintext
<!-- 娑堣垂鑰呯 - 涓汉涓績 -->
|
||
<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">
|
||
<!-- 瀵艰埅鏍忓崰浣嶇 - 闇€瑕佸寘鍚玸tatusBarHeight + 瀵艰埅鏍忛珮搴?4px -->
|
||
<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 {
|
||
// 濡傛灉鑾峰彇澶辫触锛堟湭鐧诲綍鎴栨棤妗f锛夛紝灏濊瘯鑾峰彇褰撳墠鐧诲綍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]
|
||
const itemStr = JSON.stringify(firstItem)
|
||
const itemParsed = JSON.parse(itemStr)
|
||
if (itemParsed == null) return '/static/product1.jpg'
|
||
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/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]
|
||
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 : '鍟嗗搧'
|
||
|
||
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: #ff5000;
|
||
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: #ff5000;
|
||
}
|
||
|
||
.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: #ff5000;
|
||
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: #ff5000;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn.confirm {
|
||
background-color: #4caf50;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn.review {
|
||
background-color: #ff5000;
|
||
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: #ff5000;
|
||
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>
|
||
|
||
|