Files
medical-mall/doc_mall/consumer/backup_pages/orders copy 2.uvue

1419 lines
41 KiB
Plaintext

<!-- pages/mall/consumer/orders.uvue -->
<template>
<view class="orders-page">
<!-- 椤堕儴鏍囬鏍?-->
<view class="orders-header">
<view class="header-search full-width">
<input
class="search-input"
type="text"
placeholder="鎼滅储璁㈠崟鍙锋垨鍟嗗搧鍚嶇О"
:value="searchKeyword"
@input="onSearchInput"
@confirm="onSearchConfirm"
/>
<text v-if="searchKeyword" class="search-clear" @click="clearSearch">脳</text>
<text v-else class="search-icon">馃攳</text>
</view>
</view>
<!-- 璁㈠崟鐘舵€佺瓫閫?-->
<view class="order-tabs-fixed-container">
<view
:class="['tab-item-fixed', { active: activeTab === 'all' }]"
@click="switchTab('all')"
>
<text class="tab-name">鍏ㄩ儴</text>
<view v-if="activeTab === 'all'" class="active-indicator"></view>
</view>
<scroll-view scroll-x="true" direction="horizontal" class="tab-scroll-mobile" :show-scrollbar="false" :scroll-with-animation="true">
<view class="tab-container-mobile">
<view
v-for="tab in orderTabsMobile"
:key="tab.id"
:class="['tab-item-mobile', { active: activeTab === tab.id }]"
@click="switchTab(tab.id)"
>
<text class="tab-name">{{ tab.name }}</text>
<text v-if="tab.count > 0" class="tab-count">{{ tab.count }}</text>
<view v-if="activeTab === tab.id" class="active-indicator"></view>
</view>
</view>
</scroll-view>
</view>
<!-- 璁㈠崟鍒楄〃 -->
<scroll-view
scroll-y
class="orders-content"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="loadMore"
>
<!-- 绌虹姸鎬?-->
<view v-if="!loading && orders.length === 0" class="empty-orders">
<text class="empty-icon">馃摝</text>
<text class="empty-title">鏆傛棤璁㈠崟</text>
<text class="empty-desc">鍘婚€涢€涳紝鍙戠幇蹇冧华鐨勫晢鍝?/text>
<button class="go-shopping-btn" @click="goShopping">鍘婚€涢€?/button>
</view>
<!-- 璁㈠崟鍒楄〃 -->
<view v-else class="order-list">
<view
v-for="order in orders"
:key="order.id"
class="order-card"
@click="viewOrderDetail(order.id)"
>
<!-- 璁㈠崟澶撮儴锛氭樉绀哄簵閾哄悕绉?-->
<view class="order-card-header">
<view class="shop-info">
<text class="shop-icon">馃彧</text>
<text class="shop-name">{{ order.shop_name != null && order.shop_name != '' ? order.shop_name : '鑷惀搴楅摵' }}</text>
<text class="arrow-right">鈥?/text>
</view>
<text :class="['order-status', getStatusClass(order.status)]">
{{ getStatusText(order.status) }}
</text>
</view>
<!-- 璁㈠崟鍟嗗搧 -->
<view class="order-products">
<view
v-for="product in order.products"
:key="product.id"
class="order-product"
@click="navigateToProduct(product)"
>
<image
class="product-image"
:src="product.image"
mode="aspectFill"
/>
<view class="product-info">
<view class="product-top-info">
<text class="product-name">{{ product.name }}</text>
<text class="product-spec">{{ product.spec }}</text>
</view>
<view class="product-footer">
<text class="product-price">楼{{ product.price }}</text>
<text class="product-quantity">x{{ product.quantity }}</text>
</view>
</view>
</view>
</view>
<!-- 璁㈠崟姹囨€讳俊鎭?-->
<view class="order-summary">
<text class="order-time">{{ formatDate(order.create_time) }}</text>
<view class="summary-right">
<text class="summary-label">鍏眥{ order.products.length }}浠跺晢鍝?瀹炰粯:</text>
<text class="summary-price">楼{{ order.total_amount }}</text>
</view>
</view>
<!-- 璁㈠崟鎿嶄綔 -->
<view class="order-actions" @click.stop="">
<view v-if="order.status === 1" class="action-buttons">
<button class="action-btn cancel" @click="cancelOrder(order.id)">鍙栨秷璁㈠崟</button>
<button class="action-btn pay" @click="payOrder(order.id)">绔嬪嵆鏀粯</button>
</view>
<view v-if="order.status === 2" class="action-buttons">
<button class="action-btn remind" @click="remindShipping(order.id)">鎻愰啋鍙戣揣</button>
<button class="action-btn refund" @click.stop="onApplyRefund(order)">鐢宠鍞悗</button>
</view>
<view v-if="order.status === 3" class="action-buttons">
<button class="action-btn view" @click="viewLogistics(order.id)">鏌ョ湅鐗╂祦</button>
<button class="action-btn confirm" @click="confirmReceipt(order.id)">纭鏀惰揣</button>
<button class="action-btn refund" @click.stop="onApplyRefund(order)">鐢宠鍞悗</button>
</view>
<view v-if="order.status === 4" class="action-buttons">
<button class="action-btn review" @click="goReview(order)">璇勪环</button>
<button class="action-btn refund" @click.stop="onApplyRefund(order)">鐢宠鍞悗</button>
<button class="action-btn repurchase" @click="repurchase(order)">鍐嶆璐拱</button>
</view>
<view v-if="order.status === 5" class="action-buttons">
<button class="action-btn view" @click="viewOrderDetail(order.id)">鏌ョ湅璇︽儏</button>
</view>
<!-- 鍞悗璁㈠崟锛氶€€娆句腑鍜屽凡閫€娆?-->
<view v-if="order.status === 6" class="action-buttons">
<button class="action-btn view" @click="viewOrderDetail(order.id)">鏌ョ湅璇︽儏</button>
<button class="action-btn refund" @click="viewRefundProgress(order.id)">閫€娆捐繘搴?/button>
</view>
<view v-if="order.status === 7" class="action-buttons">
<button class="action-btn view" @click="viewOrderDetail(order.id)">鏌ョ湅璇︽儏</button>
<button class="action-btn repurchase" @click="repurchase(order)">鍐嶆璐拱</button>
</view>
</view>
</view>
</view>
<!-- 鍔犺浇鏇村 -->
<view v-if="loadingMore" class="loading-more">
<view class="loading-spinner"></view>
<text>鍔犺浇涓?..</text>
</view>
<view v-if="!hasMore && orders.length > 0" class="no-more">
<text>娌℃湁鏇村璁㈠崟浜?/text>
</view>
<!-- 瀹夊叏鍖哄煙 -->
<view class="safe-area"></view>
</scroll-view>
<!-- 搴曢儴瀵艰埅 -->
<view class="tabbar-placeholder"></view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, onMounted, computed } from 'vue'
import { onShow, onLoad, onBackPress } from '@dcloudio/uni-app'
import { supabaseService } from '@/utils/supabaseService.uts'
// 瀹氫箟鏍囩椤电被鍨?
type OrderTabItem = {
id: string,
name: string,
count: number
}
// 瀹氫箟璁㈠崟浜у搧绫诲瀷
type OrderProduct = {
id: string,
name: string,
price: number,
image: string,
spec: string,
quantity: number
}
// 瀹氫箟璁㈠崟绫诲瀷
type OrderItem = {
id: string,
order_no: string,
status: number,
create_time: string,
product_amount: number,
shipping_fee: number,
total_amount: number,
merchant_id: string,
shop_name: string,
products: OrderProduct[]
}
// 鍝嶅簲寮忔暟鎹?
const orders = ref<OrderItem[]>([])
const allOrdersList = ref<OrderItem[]>([]) // Store all fetched orders for client-side filtering
const loading = ref<boolean>(false)
const loadingMore = ref<boolean>(false)
const hasMore = ref<boolean>(true)
const refreshing = ref<boolean>(false)
const page = ref<number>(1)
const activeTab = ref<string>('all')
const searchKeyword = ref<string>('')
// 璁㈠崟鏍囩椤?- 浣跨敤 ref 浠ヤ究鏁翠綋鏇挎崲
const orderTabs = ref<OrderTabItem[]>([
{ id: 'all', name: '鍏ㄩ儴', count: 0 },
{ id: 'pending', name: '寰呬粯娆?, count: 0 },
{ id: 'shipping', name: '寰呭彂璐?, count: 0 },
{ id: 'delivering', name: '寰呮敹璐?, count: 0 },
{ id: 'completed', name: '宸插畬鎴?, count: 0 },
{ id: 'aftersale', name: '鍞悗', count: 0 },
{ id: 'cancelled', name: '宸插彇娑?, count: 0 }
])
// 妯℃嫙鐘舵€佺瓫閫夛紙闄ゅ幓"鍏ㄩ儴"鍚庣殑鍏朵綑鏍囩锛?
const orderTabsMobile = computed((): OrderTabItem[] => {
return orderTabs.value.filter((tab: OrderTabItem) => tab.id !== 'all')
})
// 杈呭姪鍑芥暟锛氳幏鍙栫姸鎬佺爜
const getStatusByTab = (tabId: string): number => {
if (tabId == 'pending') return 1
if (tabId == 'shipping') return 2
if (tabId == 'delivering') return 3
if (tabId == 'completed') return 4
if (tabId == 'cancelled') return 5
if (tabId == 'aftersale') return 6 // 鍞悗鏍囩锛氶€€娆句腑
return 0
}
// 鏍煎紡鍖栬鏍煎璞′负鍙嬪ソ鐨勬枃鏈?- 蹇呴』鍦?parseSpecText 涔嬪墠瀹氫箟
function formatSpecObj(obj: any): string {
if (obj == null) return ''
if (typeof obj !== 'object') {
// 闈炲璞$被鍨嬬洿鎺ヨ繑鍥炲瓧绗︿覆褰㈠紡
if (typeof obj === 'string') return obj
if (typeof obj === 'number') return obj.toString()
return ''
}
try {
const objStr = JSON.stringify(obj)
const objParsed = JSON.parse(objStr)
if (objParsed == null) return ''
const specObj = objParsed as UTSJSONObject
// 浣跨敤 JSON.stringify 鑾峰彇鎵€鏈夐敭
const specObjStr = JSON.stringify(specObj)
const specObjForKeys = JSON.parse(specObjStr) as UTSJSONObject
// 鎵嬪姩鎻愬彇閿€煎
const parts: string[] = []
// 灏濊瘯鑾峰彇宸茬煡瀛楁
const colorVal = specObjForKeys.getString('Color')
if (colorVal != null && colorVal != '') {
parts.push('Color: ' + colorVal)
}
const sizeVal = specObjForKeys.getString('Size')
if (sizeVal != null && sizeVal != '') {
parts.push('Size: ' + sizeVal)
}
const defaultVal = specObjForKeys.getString('榛樿')
if (defaultVal != null && defaultVal != '') {
parts.push('榛樿: ' + defaultVal)
}
// 濡傛灉娌℃湁鍖归厤鍒板凡鐭ュ瓧娈碉紝灏濊瘯鐩存帴鏄剧ず JSON
if (parts.length === 0) {
// 灏濊瘯閬嶅巻瀵硅薄
const objAny = specObjForKeys as any
if (objAny != null) {
return specObjStr.replace(/[{}"]/g, '').replace(/:/g, ': ').replace(/,/g, ' | ')
}
}
return parts.join(' | ')
} catch (e) {
return ''
}
}
// 杈呭姪鍑芥暟锛氳В鏋愯鏍兼枃鏈?
function parseSpecText(specs: any): string {
if (specs == null) return ''
if (typeof specs === 'string') {
// 濡傛灉鏄?JSON 瀛楃涓诧紝灏濊瘯瑙f瀽
if (specs.startsWith('{') || specs.startsWith('[')) {
try {
const parsed = JSON.parse(specs)
if (parsed == null) return specs
return formatSpecObj(parsed)
} catch (e) {
return specs
}
}
return specs
}
// 瀵逛簬瀵硅薄绫诲瀷锛屾牸寮忓寲鏄剧ず
return formatSpecObj(specs)
}
// 杈呭姪鍑芥暟锛氭洿鏂版爣绛捐鏁?
const updateTabsCounts = (allOrders: OrderItem[]) => {
// 璁$畻鍚勭姸鎬佹暟閲?
const countAll = allOrders.length
const countPending = allOrders.filter((o: OrderItem) => o.status === 1).length
const countShipping = allOrders.filter((o: OrderItem) => o.status === 2).length
const countDelivering = allOrders.filter((o: OrderItem) => o.status === 3).length
const countCompleted = allOrders.filter((o: OrderItem) => o.status === 4).length
const countCancelled = allOrders.filter((o: OrderItem) => o.status === 5).length
// 鍞悗璁㈠崟锛氬寘鎷€€娆句腑(6)鍜屽凡閫€娆?7)
const countAftersale = allOrders.filter((o: OrderItem) => o.status === 6 || o.status === 7).length
// 鏇存柊鏁扮粍鍏冪礌
orderTabs.value[0].count = countAll
orderTabs.value[1].count = countPending
orderTabs.value[2].count = countShipping
orderTabs.value[3].count = countDelivering
orderTabs.value[4].count = countCompleted
orderTabs.value[5].count = countAftersale
orderTabs.value[6].count = countCancelled
}
// 杈呭姪鍑芥暟锛氭寜鏍囩绛涢€夎鍗?
const filterOrdersByTab = () => {
if (activeTab.value === 'all') {
orders.value = allOrdersList.value
} else if (activeTab.value === 'aftersale') {
// 鍞悗鏍囩锛氭樉绀洪€€娆句腑(6)鍜屽凡閫€娆?7)鐨勮鍗?
orders.value = allOrdersList.value.filter((o: OrderItem) => {
return o.status === 6 || o.status === 7
})
} else {
const targetStatus = getStatusByTab(activeTab.value)
orders.value = allOrdersList.value.filter((o: OrderItem) => {
return o.status === targetStatus
})
}
}
// 鍔犺浇璁㈠崟鏁版嵁
const loadOrders = async () => {
loading.value = true
try {
// Fetch all orders from Supabase (status=0)
const fetchedOrders = await supabaseService.getOrders(0)
console.log('[loadOrders] 鑾峰彇鍒拌鍗曟暟閲?', fetchedOrders.length)
// Map to View Model
const mappedOrders: OrderItem[] = []
for (let i = 0; i < fetchedOrders.length; i++) {
const order = fetchedOrders[i]
// 浣跨敤 JSON 搴忓垪鍖栬浆鎹?
const orderStr = JSON.stringify(order)
const orderParsed = JSON.parse(orderStr)
if (orderParsed == null) continue
const orderObj = orderParsed as UTSJSONObject
const itemsRaw = orderObj.get('ml_order_items')
const productsList: OrderProduct[] = []
console.log('[loadOrders] 璁㈠崟鍟嗗搧鏁版嵁:', itemsRaw)
if (itemsRaw != null) {
// 鍏堟鏌ユ槸鍚︿负鏁扮粍
if (Array.isArray(itemsRaw)) {
const items = itemsRaw as any[]
console.log('[loadOrders] 鍟嗗搧鏁伴噺:', items.length)
for (let j = 0; j < items.length; j++) {
const item = items[j]
const itemStr = JSON.stringify(item)
const itemParsed = JSON.parse(itemStr)
if (itemParsed == null) continue
const itemObj = itemParsed as UTSJSONObject
const specRaw = itemObj.get('specifications')
const specText = specRaw != null ? parseSpecText(specRaw) : ''
const productId = itemObj.getString('product_id')
const productName = itemObj.getString('product_name')
const price = itemObj.getNumber('price')
const imageUrl = itemObj.getString('image_url')
const quantity = itemObj.getNumber('quantity')
console.log('[loadOrders] 鍟嗗搧:', productName, '鍥剧墖:', imageUrl, '瑙勬牸:', specText)
const productItem: OrderProduct = {
id: productId ?? '',
name: productName ?? '鏈煡鍟嗗搧',
price: price ?? 0,
image: imageUrl ?? '/static/default-product.png',
spec: specText,
quantity: quantity ?? 1
}
productsList.push(productItem)
}
}
}
const orderId = orderObj.getString('id')
const orderNo = orderObj.getString('order_no')
const orderStatus = orderObj.getNumber('order_status')
const createdAt = orderObj.getString('created_at')
const productAmount = orderObj.getNumber('product_amount')
const shippingFee = orderObj.getNumber('shipping_fee')
const totalAmount = orderObj.getNumber('total_amount')
const paidAmount = orderObj.getNumber('paid_amount')
const merchantId = orderObj.getString('merchant_id')
// 浠庡叧鑱旀煡璇㈢殑 ml_shops 琛ㄨ幏鍙栧簵閾哄悕绉?
let shopName = '鑷惀搴楅摵'
const shopsRaw = orderObj.get('ml_shops')
if (shopsRaw != null) {
const shopStr = JSON.stringify(shopsRaw)
const shopParsed = JSON.parse(shopStr)
if (shopParsed != null) {
const shopObj = shopParsed as UTSJSONObject
const shopNameFromDb = shopObj.getString('shop_name')
if (shopNameFromDb != null && shopNameFromDb != '') {
shopName = shopNameFromDb
}
}
} else if (merchantId != null && merchantId != '') {
shopName = '鍟嗗搴楅摵'
}
console.log('[loadOrders] 璁㈠崟鍙?', orderNo, '搴楅摵:', shopName, '鍟嗗搧鏁?', productsList.length)
// 濡傛灉娌℃湁鍟嗗搧鏁版嵁锛屾坊鍔犱竴涓崰浣嶅晢鍝?
if (productsList.length === 0) {
const placeholderProduct: OrderProduct = {
id: 'placeholder',
name: '璁㈠崟鍟嗗搧',
price: totalAmount ?? paidAmount ?? 0,
image: '/static/default-product.png',
spec: '',
quantity: 1
}
productsList.push(placeholderProduct)
}
const mappedOrder: OrderItem = {
id: orderId ?? '',
order_no: orderNo ?? '',
status: orderStatus ?? 1,
create_time: createdAt ?? '',
product_amount: productAmount ?? 0,
shipping_fee: shippingFee ?? 0,
total_amount: totalAmount ?? paidAmount ?? 0,
merchant_id: merchantId ?? '',
shop_name: shopName,
products: productsList
}
mappedOrders.push(mappedOrder)
}
// Sort by created_at desc - 鐩存帴浣跨敤 OrderItem 绫诲瀷璁块棶灞炴€?
mappedOrders.sort((a: OrderItem, b: OrderItem) => {
const timeA = new Date(a.create_time).getTime()
const timeB = new Date(b.create_time).getTime()
return timeB - timeA
})
allOrdersList.value = mappedOrders
// Update tab counts
updateTabsCounts(mappedOrders)
// Apply current tab filter
filterOrdersByTab()
} catch (err) {
console.error('鍔犺浇璁㈠崟寮傚父:', err)
uni.showToast({ title: '鍔犺浇璁㈠崟澶辫触', icon: 'none' })
} finally {
loading.value = false
}
}
// 鐢熷懡鍛ㄦ湡
onLoad((options) => {
if (options == null) return
const statusVal = options['status']
if (statusVal != null) {
const status = statusVal as string
if (['all', 'pending', 'shipping', 'delivering', 'completed', 'aftersale', 'cancelled'].includes(status)) {
activeTab.value = status
}
}
const typeVal = options['type']
if (typeVal != null) {
const type = typeVal as string
if (type === 'pending') activeTab.value = 'pending'
else if (type === 'shipped') activeTab.value = 'delivering' // 鏄犲皠鍒板緟鏀惰揣
else if (type === 'review') activeTab.value = 'completed' // 鏄犲皠鍒板凡瀹屾垚
else if (type === 'refund') activeTab.value = 'all' // 鐢宠鍞悗榛樿鏄剧ず鍏ㄩ儴
}
})
onShow(() => {
loadOrders()
})
const formatDate = (isoString: string): string => {
if (isoString == '') return ''
const date = new Date(isoString)
return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
// 杈呭姪鍑芥暟锛氳幏鍙栧綋鍓嶈鍗曟暟鎹紙蹇呴』鍦?performSearch 涔嬪墠瀹氫箟锛?
function getCurrentOrderData(): OrderItem[] {
return allOrdersList.value
}
// 鎼滅储鎵ц鍑芥暟锛堝繀椤诲湪 onSearchInput 绛変箣鍓嶅畾涔夛級
const performSearch = () => {
const keyword = searchKeyword.value.trim().toLowerCase()
if (keyword == '') {
loadOrders()
return
}
// 鍦ㄥ綋鍓嶈鍗曟暟鎹腑鎼滅储
const allOrders = getCurrentOrderData()
const filtered = allOrders.filter((order: any) => {
const orderObj = order as Record<string, any>
// 鎼滅储璁㈠崟鍙?
const orderNo = orderObj['order_no'] as string
if (orderNo != null && orderNo.toLowerCase().includes(keyword)) {
return true
}
// 鎼滅储鍟嗗搧鍚嶇О
const products = orderObj['products']
if (products != null && Array.isArray(products)) {
return products.some((product: any) => {
const productObj = product as Record<string, any>
const name = productObj['name'] as string
return name != null && name.toLowerCase().includes(keyword)
})
}
return false
})
orders.value = filtered
}
// 鎼滅储鐩稿叧鍑芥暟
const onSearchInput = (e: any) => {
const eObj = e as Record<string, any>
const detail = eObj['detail'] as Record<string, any>
searchKeyword.value = detail['value'] as string
performSearch()
}
const onSearchConfirm = () => {
performSearch()
}
const clearSearch = () => {
searchKeyword.value = ''
performSearch()
}
const formatSpec = (specs: any): string => {
if (specs == null) return ''
if (typeof specs === 'string') return specs
if (typeof specs === 'object') {
return JSON.stringify(specs)
}
return ''
}
// 鍒囨崲鏍囩
const switchTab = (tabId: string) => {
activeTab.value = tabId
filterOrdersByTab()
}
// 鑾峰彇鐘舵€佹枃鏈?
const getStatusText = (status: number): string => {
if (status == 1) return '寰呬粯娆?
if (status == 2) return '寰呭彂璐?
if (status == 3) return '寰呮敹璐?
if (status == 4) return '宸插畬鎴?
if (status == 5) return '宸插彇娑?
if (status == 6) return '閫€娆句腑'
if (status == 7) return '宸查€€娆?
return '鏈煡鐘舵€?
}
// 鑾峰彇鐘舵€佺被鍚?
const getStatusClass = (status: number): string => {
if (status == 1) return 'status-pending'
if (status == 2) return 'status-shipping'
if (status == 3) return 'status-delivering'
if (status == 4) return 'status-completed'
if (status == 5) return 'status-cancelled'
if (status == 6) return 'status-refunding'
if (status == 7) return 'status-refunded'
return 'status-unknown'
}
// 涓嬫媺鍒锋柊
const onRefresh = () => {
refreshing.value = true
setTimeout(() => {
loadOrders()
refreshing.value = false
uni.showToast({
title: '鍒锋柊鎴愬姛',
icon: 'success'
})
}, 1000)
}
// 涓婃媺鍔犺浇鏇村
const loadMore = () => {
if (loadingMore.value || !hasMore.value) return
// 鏆傛湭瀹炵幇鍒嗛〉锛岀洿鎺ヨ繑鍥?
hasMore.value = false
}
// 璁㈠崟鎿嶄綔鍑芥暟
const cancelOrder = (orderId: string) => {
uni.showModal({
title: '纭鍙栨秷',
content: '纭畾瑕佸彇娑堟璁㈠崟鍚楋紵',
success: (res) => {
if (res.confirm) {
// 杩欓噷搴旇鏄疄闄呯殑API璋冪敤
uni.showToast({
title: '璁㈠崟宸插彇娑?,
icon: 'success'
})
// 鏇存柊璁㈠崟鐘舵€?
const index = orders.value.findIndex((o: any) => {
const obj = o as Record<string, any>
return obj['id'] === orderId
})
if (index !== -1) {
const orderObj = orders.value[index] as Record<string, any>
orderObj['status'] = 5
orders.value = [...orders.value]
}
}
}
})
}
const payOrder = (orderId: string) => {
uni.navigateTo({
url: `/pages/mall/consumer/payment?orderId=${orderId}`
})
}
const remindShipping = (orderId: string) => {
uni.showToast({
title: '宸叉彁閱掑崠瀹跺彂璐?,
icon: 'success'
})
}
const viewLogistics = (orderId: string) => {
uni.navigateTo({
url: `/pages/mall/consumer/logistics?orderId=${orderId}`
})
}
// goReview 蹇呴』鍦?doConfirmReceipt 涔嬪墠瀹氫箟锛屽洜涓?doConfirmReceipt 浼氳皟鐢ㄥ畠
const goReview = (order: any) => {
const orderObj = order as Record<string, any>
const products = orderObj['products'] as any[]
const productIds = products.map((p: any) => {
const pObj = p as Record<string, any>
const pid = pObj['id']
return pid != null ? pid as string : ''
}).join(',')
const orderId = orderObj['id'] as string
uni.navigateTo({
url: `/pages/mall/consumer/review?orderId=${orderId}&productIds=${productIds}`
})
}
const doConfirmReceipt = async (orderId: string) => {
uni.showLoading({ title: '澶勭悊涓?..' })
try {
const result = await supabaseService.confirmReceipt(orderId)
uni.hideLoading()
if (result.success) {
uni.showToast({
title: '鏀惰揣鎴愬姛',
icon: 'success'
})
// 鏇存柊鏈湴鐘舵€?
const index = orders.value.findIndex((o: any) => {
const obj = o as Record<string, any>
return obj['id'] === orderId
})
if (index !== -1) {
const orderObj = orders.value[index] as Record<string, any>
orderObj['status'] = 4
orders.value = [...orders.value]
}
// 璺宠浆鍒拌瘎浠烽〉闈?
setTimeout(() => {
const order = orders.value.find((o: any) => {
const obj = o as Record<string, any>
return obj['id'] === orderId
})
if (order != null) {
goReview(order)
}
}, 1000)
} else {
uni.showToast({
title: result.error ?? '纭鏀惰揣澶辫触',
icon: 'none'
})
}
} catch (e) {
uni.hideLoading()
uni.showToast({
title: '绯荤粺寮傚父',
icon: 'none'
})
}
}
const confirmReceipt = (orderId: string) => {
uni.showModal({
title: '纭鏀惰揣',
content: '璇风‘璁ゆ偍宸叉敹鍒板晢鍝侊紝涓斿晢鍝佹棤璇?,
success: (res) => {
if (res.confirm) {
doConfirmReceipt(orderId)
}
}
})
}
const repurchase = (order: any) => {
uni.showModal({
title: '鍐嶆璐拱',
content: '纭畾瑕佸皢杩欎簺鍟嗗搧鍔犲叆璐墿杞﹀悧锛?,
success: (res) => {
if (res.confirm) {
// 杩欓噷搴旇鏄疄闄呯殑API璋冪敤
uni.showToast({
title: '宸插姞鍏ヨ喘鐗╄溅',
icon: 'success'
})
}
}
})
}
const viewOrderDetail = (orderId: string) => {
uni.navigateTo({
url: `/pages/mall/consumer/order-detail?id=${orderId}`
})
}
const onApplyRefund = (order: any) => {
const orderObj = order as Record<string, any>
const orderId = orderObj['id']
uni.navigateTo({
url: `/pages/mall/consumer/apply-refund?orderId=${orderId}`
})
}
// 瀵艰埅鍑芥暟
const navigateToSearch = () => {
uni.navigateTo({ url: '/pages/mall/consumer/search' })
}
const navigateToProduct = (product: any) => {
const productObj = product as Record<string, any>
const productId = productObj['id']
uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${productId}` })
}
const goShopping = () => {
uni.switchTab({ url: '/pages/mall/consumer/index' })
}
</script>
<style>
.orders-page {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: #f5f5f5;
position: relative;
overflow: hidden;
}
/* 澶撮儴 */
.orders-header {
background-color: white;
padding: 15px;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid #eee;
z-index: 10;
flex-shrink: 0;
}
.header-search.full-width {
display: flex;
align-items: center;
position: relative;
width: 100%;
}
.search-input {
flex: 1;
height: 36px;
border: 1px solid #ddd;
border-radius: 18px;
padding: 0 40px 0 16px;
font-size: 14px;
background-color: #f5f5f5;
color: #333;
width: 100%;
}
.search-input::placeholder {
color: #999;
font-size: 12px;
}
.search-input:focus {
border-color: #ff5000;
background-color: white;
}
.search-icon {
position: absolute;
right: 12px;
font-size: 18px;
color: #999;
}
.search-clear {
position: absolute;
right: 12px;
font-size: 20px;
color: #999;
width: 20px;
height: 20px;
line-height: 18px;
text-align: center;
border-radius: 10px; /* fixed 50% */
background-color: #ddd;
/* cursor: pointer; removed */
}
/* 鏍囩椤?*/
.order-tabs-fixed-container {
background-color: #ffffff;
border-bottom: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
z-index: 10;
height: 50px;
flex-shrink: 0;
}
.tab-item-fixed {
padding: 0 15px;
text-align: center;
position: relative;
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
flex-shrink: 0;
min-width: 60px;
height: 100%;
}
.tab-item-fixed.active {
color: #ff5000;
font-weight: bold;
}
.tab-scroll-mobile {
flex: 1;
/* UVue 瀹夊崜绔?scroll-view 闇€瑕佽缃叿浣撻珮搴︽垨鐢辩埗绾ф拺寮€ */
height: 50px;
white-space: nowrap;
}
.tab-container-mobile {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
height: 100%;
/* 鏄惧紡瀹氫箟瓒冲瀹斤紝寮哄埗瑙﹀彂 scroll-view 婊氬姩 */
width: auto;
min-width: 100%;
}
.tab-item-mobile {
padding: 0 15px;
text-align: center;
position: relative;
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
flex-shrink: 0;
height: 100%;
}
.tab-item-mobile.active {
color: #ff5000;
font-weight: bold;
}
.active-indicator {
position: absolute;
bottom: 0px;
left: 10px;
right: 10px;
height: 3px;
background-color: #ff5000;
border-radius: 2px;
}
.tab-name {
font-size: 14px;
}
.tab-count {
margin-left: 4px;
background-color: #ff5000;
color: white;
font-size: 10px;
padding: 1px 4px;
border-radius: 8px;
min-width: 12px;
text-align: center;
}
/* 鍐呭鍖?*/
.orders-content {
flex: 1;
height: 0;
width: 100%;
}
/* 绌虹姸鎬?*/
.empty-orders {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 20px;
}
.empty-icon {
font-size: 80px;
color: #ddd;
margin-bottom: 20px;
}
.empty-title {
font-size: 18px;
color: #666;
margin-bottom: 10px;
}
.empty-desc {
font-size: 14px;
color: #999;
margin-bottom: 30px;
}
.go-shopping-btn {
background-color: #ff5000;
color: white;
border: none;
border-radius: 25px;
padding: 10px 40px;
font-size: 16px;
}
/* 璁㈠崟鍒楄〃 */
.order-list {
padding: 10px;
}
.order-card {
background-color: white;
border-radius: 10px;
margin-bottom: 10px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
/* 璁㈠崟澶撮儴 */
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #f5f5f5;
}
.order-no {
font-size: 14px;
color: #666;
}
.order-status {
font-size: 14px;
font-weight: bold;
}
.status-pending {
color: #ff5000;
}
.status-shipping {
color: #ff9500;
}
.status-delivering {
color: #007aff;
}
.status-completed {
color: #34c759;
}
.status-cancelled {
color: #999;
}
.status-refunding {
color: #ff5000;
}
.status-refunded {
color: #999;
}
/* 璁㈠崟鍟嗗搧 */
.order-products {
padding: 15px;
}
.order-product {
display: flex;
flex-direction: row; /* 鏄惧紡澹版槑妯悜鎺掑垪 */
margin-bottom: 15px;
width: 100%;
}
.order-product:last-child {
margin-bottom: 0;
}
.product-image {
width: 80px;
height: 80px;
border-radius: 8px;
margin-right: 12px;
flex-shrink: 0; /* 闃叉鍥剧墖琚帇缂?*/
}
.product-info {
flex: 1; /* 鍗犳嵁鍙充晶鍓╀綑鎵€鏈夌┖闂?*/
display: flex;
flex-direction: column;
justify-content: space-between;
height: 80px;
overflow: hidden; /* 闃叉鏂囧瓧婧㈠嚭 */
}
.product-top-info {
display: flex;
flex-direction: column;
width: 100%;
}
.product-name {
font-size: 14px;
color: #333;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
lines: 2;
}
.product-spec {
font-size: 12px;
color: #999;
margin-top: 2px;
}
.product-footer {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: auto;
}
.product-price {
font-size: 16px;
color: #ff5000;
font-weight: bold;
}
.product-quantity {
font-size: 13px;
color: #999;
}
/* 璁㈠崟淇℃伅 */
.order-info {
padding: 15px;
border-top: 1px solid #f5f5f5;
border-bottom: 1px solid #f5f5f5;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-row.total {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #f5f5f5;
}
.info-label {
font-size: 14px;
color: #666;
}
.info-value {
font-size: 14px;
color: #333;
}
.total-price {
font-size: 18px;
color: #ff5000;
font-weight: bold;
}
/* 璁㈠崟鎿嶄綔 */
.order-actions {
padding: 15px;
}
.action-buttons {
display: flex;
justify-content: flex-end;
/* gap: 10px; removed */
}
.action-btn {
padding: 6px 15px;
border-radius: 15px;
font-size: 13px;
border: 1px solid;
background-color: transparent; /* fixed background: none */
margin-left: 10px; /* alternative to gap */
}
.action-btn.cancel {
color: #666;
border-color: #ccc;
}
.action-btn.pay {
color: #ff5000;
border-color: #ff5000;
}
.action-btn.remind {
color: #666;
border-color: #ccc;
}
.action-btn.view {
color: #666;
border-color: #ccc;
}
.action-btn.confirm {
color: #34c759;
border-color: #34c759;
}
.action-btn.review {
color: #ff9500;
border-color: #ff9500;
}
.action-btn.repurchase {
color: #ff5000;
border-color: #ff5000;
}
/* 鍔犺浇鏇村 */
.loading-more {
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-spinner {
width: 24px;
height: 24px;
border: 2px solid #f0f5ff;
border-top-color: #ff5000;
border-radius: 12px;
margin-bottom: 10px;
}
.no-more {
text-align: center;
color: #999;
font-size: 13px;
padding: 20px 0;
}
/* 瀹夊叏鍖哄煙 */
.safe-area {
height: 20px;
}
/* 搴曢儴瀵艰埅鍗犱綅 */
.tabbar-placeholder {
height: 50px;
background-color: #f5f5f5;
}
/* 鍝嶅簲寮忛€傞厤 */
@media screen and (min-width: 768px) {
.order-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.order-card {
width: 48%;
margin: 0 1% 20px 1%;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
}
@media screen and (min-width: 1200px) {
.order-card {
width: 31%;
margin: 0 1% 20px 1%;
}
}
/* 璁㈠崟鍗$墖鏂版牱寮?*/
.order-card-header {
padding: 12px 15px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #f9f9f9;
}
.shop-info {
display: flex;
flex-direction: row;
align-items: center;
}
.shop-icon {
font-size: 16px;
margin-right: 6px;
}
.shop-name {
font-size: 14px;
font-weight: bold;
color: #333;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
lines: 1;
}
.arrow-right {
font-size: 14px;
color: #ccc;
margin-left: 4px;
}
.product-title-row, .product-spec-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
}
.product-title-row {
margin-bottom: 4px;
}
.product-spec-row {
margin-top: 2px;
}
.order-summary {
padding: 10px 15px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
border-top: 1px solid #f9f9f9;
}
.order-time {
font-size: 12px;
color: #999;
}
.summary-right {
display: flex;
flex-direction: row;
align-items: center;
}
.summary-label {
font-size: 12px;
color: #666;
margin-right: 5px;
}
.summary-price {
font-size: 16px;
font-weight: bold;
color: #333;
}
@media screen and (max-width: 320px) {
.tab-item {
padding: 0 10px;
margin-right: 5px;
}
.action-btn {
padding: 6px 10px;
font-size: 12px;
}
}
@media screen and (min-width: 415px) {
.order-card {
margin-bottom: 15px;
}
}
</style>