1361 lines
38 KiB
Plaintext
1361 lines
38 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">
|
|
<scroll-view scroll-x class="tab-scroll" :show-scrollbar="false">
|
|
<view class="tab-container">
|
|
<view
|
|
v-for="tab in orderTabs"
|
|
:key="tab.id"
|
|
:class="['tab-item', { 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>
|
|
</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: 'cancelled', name: '宸插彇娑?, count: 0 }
|
|
])
|
|
|
|
// Removed Mock Data
|
|
|
|
|
|
// 杈呭姪鍑芥暟锛氳幏鍙栫姸鎬佺爜
|
|
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
|
|
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
|
|
|
|
// 鏇存柊鏁扮粍鍏冪礌
|
|
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 = countCancelled
|
|
}
|
|
|
|
// 杈呭姪鍑芥暟锛氭寜鏍囩绛涢€夎鍗?
|
|
const filterOrdersByTab = () => {
|
|
if (activeTab.value === 'all') {
|
|
orders.value = allOrdersList.value
|
|
} 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', '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 '宸插彇娑?
|
|
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'
|
|
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: 100vh;
|
|
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;
|
|
/* position: sticky; removed */
|
|
/* top: 0; removed */
|
|
z-index: 10;
|
|
}
|
|
|
|
.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 {
|
|
background-color: #ffffff;
|
|
border-bottom: 1px solid #e5e5e5;
|
|
/* position: sticky; removed */
|
|
/* top: 50px; removed */
|
|
z-index: 10;
|
|
}
|
|
|
|
.tab-scroll {
|
|
width: 100%;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.tab-container {
|
|
display: flex;
|
|
flex-direction: row;
|
|
padding: 0 10px;
|
|
/* width: max-content; removed */
|
|
/* min-width: 100%; removed */
|
|
}
|
|
|
|
.tab-item {
|
|
/* 绉婚櫎 flex: 1锛屾敼涓鸿嚜閫傚簲瀹藉害鎴栧浐瀹氭渶灏忓搴?*/
|
|
padding: 15px 15px; /* 澧炲姞姘村钩鍐呰竟璺?*/
|
|
text-align: center;
|
|
position: relative;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
white-space: nowrap; /* 闃叉鏂囧瓧鎹㈣ */
|
|
flex-shrink: 0; /* 闃叉琚帇缂?*/
|
|
}
|
|
|
|
.tab-item.active {
|
|
color: #ff5000;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.tab-item.active::after {
|
|
/* content: ''; removed */
|
|
/* content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 2px;
|
|
background-color: #ff5000; */
|
|
}
|
|
|
|
.active-indicator {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 2px;
|
|
background-color: #ff5000;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
/* 璁㈠崟鍟嗗搧 */
|
|
.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>
|
|
|
|
|