consumerm模块完成度90%,完善消费者和商家端数据库表,商品、聊天、订单数据对接好了supabase,和商家端对接了聊天功能,安卓端编译通过了css样式,剩余几个页面在处理函数规范问题

This commit is contained in:
cyh666666
2026-02-24 17:17:49 +08:00
parent e2f1dfb097
commit e606c597ca
174 changed files with 37917 additions and 4444 deletions

View File

@@ -14,7 +14,7 @@
</view>
<!-- 配送信息 -->
<view v-if="order.order_status >= 2" class="delivery-info card">
<view v-if="order != null && (order?.order_status ?? 0) >= 2" class="delivery-info card">
<view class="delivery-header">
<text class="section-title">配送信息</text>
</view>
@@ -22,17 +22,17 @@
<view class="address-icon">📍</view>
<view class="address-content">
<view class="address-user">
<text class="recipient">{{ deliveryAddress.name }}</text>
<text class="phone">{{ deliveryAddress.phone }}</text>
<text class="recipient">{{ deliveryAddress?.name ?? '' }}</text>
<text class="phone">{{ deliveryAddress?.phone ?? '' }}</text>
</view>
<text class="address-detail">{{ getFullAddress(deliveryAddress) }}</text>
<text class="address-detail">{{ getFullAddress(deliveryAddress as any) }}</text>
</view>
</view>
<!-- 如果有物流信息显示 -->
<view v-if="deliveryInfo.tracking_no" class="courier-info">
<view v-if="deliveryInfo != null && deliveryInfo?.tracking_no != ''" class="courier-info">
<text class="courier-label">物流单号:</text>
<text class="courier-value">{{ deliveryInfo.tracking_no }}</text>
<text class="copy-btn" @click="copyText(deliveryInfo.tracking_no)">复制</text>
<text class="courier-value">{{ deliveryInfo?.tracking_no ?? '' }}</text>
<text class="copy-btn" @click="copyText(deliveryInfo?.tracking_no ?? '')">复制</text>
</view>
</view>
@@ -44,7 +44,7 @@
<text class="arrow-right">></text>
</view>
<view v-for="item in orderItems" :key="item.id" class="product-item" @click="goToProduct(item.product_id)">
<image :src="item.image_url || '/static/default-product.png'" class="product-image" mode="aspectFill"/>
<image :src="item.image_url != null && item.image_url != '' ? item.image_url : '/static/default-product.png'" class="product-image" mode="aspectFill"/>
<view class="product-info">
<text class="product-name">{{ item.product_name }}</text>
<text v-if="item.specifications" class="product-spec">{{ getSpecText(item.specifications) }}</text>
@@ -57,56 +57,56 @@
</view>
<!-- 订单信息 -->
<view class="order-info card">
<view class="order-info card" v-if="order != null">
<view class="info-row">
<text class="info-label">订单编号</text>
<text class="info-value copyable" @click="copyText(order.order_no)">{{ order.order_no }} <text class="copy-icon">📄</text></text>
<text class="info-value copyable" @click="copyText(order?.order_no ?? '')">{{ order?.order_no ?? '' }} <text class="copy-icon">📄</text></text>
</view>
<view class="info-row">
<text class="info-label">下单时间</text>
<text class="info-value">{{ formatTime(order.created_at) }}</text>
<text class="info-value">{{ formatTime(order?.created_at ?? '') }}</text>
</view>
<view class="info-row" v-if="order.payment_method">
<view class="info-row" v-if="order?.payment_method != null && order?.payment_method != ''">
<text class="info-label">支付方式</text>
<text class="info-value">{{ getPaymentMethodText(order.payment_method) }}</text>
<text class="info-value">{{ getPaymentMethodText(order?.payment_method as any) }}</text>
</view>
<view class="info-row" v-if="order.paid_at">
<view class="info-row" v-if="order?.paid_at != null && order?.paid_at != ''">
<text class="info-label">支付时间</text>
<text class="info-value">{{ formatTime(order.paid_at) }}</text>
<text class="info-value">{{ formatTime(order?.paid_at ?? '') }}</text>
</view>
<view class="info-row" v-if="order.shipped_at">
<view class="info-row" v-if="order?.shipped_at != null && order?.shipped_at != ''">
<text class="info-label">发货时间</text>
<text class="info-value">{{ formatTime(order.shipped_at) }}</text>
<text class="info-value">{{ formatTime(order?.shipped_at ?? '') }}</text>
</view>
<view class="info-row" v-if="order.completed_at">
<view class="info-row" v-if="order?.completed_at != null && order?.completed_at != ''">
<text class="info-label">完成时间</text>
<text class="info-value">{{ formatTime(order.completed_at) }}</text>
<text class="info-value">{{ formatTime(order?.completed_at ?? '') }}</text>
</view>
</view>
<!-- 费用明细 -->
<view class="cost-detail card">
<view class="cost-detail card" v-if="order != null">
<view class="cost-row">
<text class="cost-label">商品总额</text>
<text class="cost-value">¥{{ order.product_amount }}</text>
<text class="cost-value">¥{{ order?.product_amount ?? 0 }}</text>
</view>
<view class="cost-row">
<text class="cost-label">运费</text>
<text class="cost-value">+¥{{ order.shipping_fee || 0 }}</text>
<text class="cost-value">+¥{{ order?.shipping_fee != null ? order?.shipping_fee : 0 }}</text>
</view>
<view class="cost-row" v-if="order.discount_amount > 0">
<view class="cost-row" v-if="(order?.discount_amount ?? 0) > 0">
<text class="cost-label">优惠金额</text>
<text class="cost-value">-¥{{ order.discount_amount }}</text>
<text class="cost-value">-¥{{ order?.discount_amount ?? 0 }}</text>
</view>
<view class="cost-row total">
<text class="cost-label">实付金额</text>
<text class="cost-value price">¥{{ order.total_amount }}</text>
<text class="cost-value price">¥{{ order?.total_amount ?? 0 }}</text>
</view>
</view>
</scroll-view>
<!-- 底部操作 -->
<view class="bottom-actions">
<view class="bottom-actions" v-if="order != null">
<view class="action-left">
<view class="service-btn" @click="contactService">
<text class="service-icon">🎧</text>
@@ -114,20 +114,20 @@
</view>
</view>
<view class="action-right">
<button v-if="order.order_status === 1" class="btn primary" @click="payOrder">立即支付</button>
<button v-if="order.order_status === 1" class="btn" @click="cancelOrder">取消订单</button>
<button v-if="order?.order_status === 1" class="btn primary" @click="payOrder">立即支付</button>
<button v-if="order?.order_status === 1" class="btn" @click="cancelOrder">取消订单</button>
<button v-if="order.order_status === 2" class="btn" @click="remindDelivery">提醒发货</button>
<button v-if="order.order_status === 2" class="btn" @click="applyRefund">申请退款</button>
<button v-if="order?.order_status === 2" class="btn" @click="remindDelivery">提醒发货</button>
<button v-if="order?.order_status === 2" class="btn" @click="applyRefund">申请退款</button>
<button v-if="order.order_status === 3" class="btn primary" @click="confirmReceive">确认收货</button>
<button v-if="order.order_status === 3" class="btn" @click="viewLogistics">查看物流</button>
<button v-if="order?.order_status === 3" class="btn primary" @click="confirmReceive">确认收货</button>
<button v-if="order?.order_status === 3" class="btn" @click="viewLogistics">查看物流</button>
<button v-if="order.order_status === 4" class="btn primary" @click="goToReview">评价</button>
<button v-if="order.order_status === 4" class="btn" @click="rePurchase">再次购买</button>
<button v-if="order.order_status === 4" class="btn" @click="applyAfterSales">申请售后</button>
<button v-if="order?.order_status === 4" class="btn primary" @click="goToReview">评价</button>
<button v-if="order?.order_status === 4" class="btn" @click="rePurchase">再次购买</button>
<button v-if="order?.order_status === 4" class="btn" @click="applyAfterSales">申请售后</button>
<button v-if="order.order_status === 5" class="btn" @click="rePurchase">重新购买</button>
<button v-if="order?.order_status === 5" class="btn" @click="rePurchase">重新购买</button>
</view>
</view>
</view>
@@ -139,60 +139,57 @@ import { onLoad } from '@dcloudio/uni-app'
import { supabaseService } from '@/utils/supabaseService.uts'
import supa from '@/components/supadb/aksupainstance.uts'
// 定义订单类型
type OrderType = {
order_no: string,
order_status: number,
total_amount: number,
product_amount: number,
shipping_fee: number,
discount_amount: number,
payment_method: string,
created_at: string,
paid_at: string,
shipped_at: string,
completed_at: string,
merchant_id: string,
shipping_address: any
}
type OrderItemType = {
id: string,
product_id: string,
product_name: string,
image_url: string,
price: number,
quantity: number,
specifications: any
}
type AddressType = {
name: string,
phone: string,
province: string,
city: string,
district: string,
detail: string,
address: string
}
type DeliveryInfoType = {
tracking_no: string
}
const orderId = ref('')
const order = ref<any>({})
const orderItems = ref<any[]>([])
const order = ref<OrderType | null>(null)
const orderItems = ref<OrderItemType[]>([])
const shopName = ref('店铺名称')
const deliveryAddress = ref<any>({})
const deliveryInfo = ref<any>({})
const deliveryAddress = ref<AddressType | null>(null)
const deliveryInfo = ref<DeliveryInfoType | null>(null)
onLoad((options) => {
if (options['id']) {
orderId.value = options['id'] as string
loadOrderDetail()
} else if (options['orderId']) {
orderId.value = options['orderId'] as string
loadOrderDetail()
}
})
const loadOrderDetail = async () => {
uni.showLoading({ title: '加载中' })
try {
const data = await supabaseService.getOrderDetail(orderId.value)
if (data) {
order.value = data
orderItems.value = data.ml_order_items || []
deliveryAddress.value = data.shipping_address || {}
// 获取店铺信息
if (data.merchant_id) {
loadShopInfo(data.merchant_id)
}
} else {
uni.showToast({ title: '订单不存在', icon: 'none' })
}
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
uni.hideLoading()
}
}
const loadShopInfo = async (merchantId: string) => {
const { data } = await supa
.from('ml_shops')
.select('shop_name')
.eq('merchant_id', merchantId)
.single()
if (data) {
shopName.value = data['shop_name'] as string
}
}
// 辅助函数
const getStatusText = () => {
const status = order.value.order_status
// 辅助函数 - 必须在调用前定义
const getStatusText = (): string => {
const status = order.value?.order_status ?? 0
if (status == 1) return '待付款'
if (status == 2) return '待发货'
if (status == 3) return '待收货'
@@ -203,8 +200,8 @@ const getStatusText = () => {
return '未知状态'
}
const getStatusDesc = () => {
const status = order.value.order_status
const getStatusDesc = (): string => {
const status = order.value?.order_status ?? 0
if (status == 1) return '请尽快完成支付'
if (status == 2) return '商家正在打包商品'
if (status == 3) return '商品正在赶往您的地址'
@@ -215,8 +212,8 @@ const getStatusDesc = () => {
return ''
}
const getStatusIcon = () => {
const status = order.value.order_status
const getStatusIcon = (): string => {
const status = order.value?.order_status ?? 0
if (status === 1) return '💳'
if (status === 2) return '📦'
if (status === 3) return '🚚'
@@ -224,43 +221,85 @@ const getStatusIcon = () => {
return '📝'
}
const getStatusClass = () => {
const status = order.value.order_status
const getStatusClass = (): string => {
const status = order.value?.order_status ?? 0
return `status-${status}`
}
const getFullAddress = (addr: any) => {
if (!addr) return ''
const getFullAddress = (addr: any): string => {
if (addr == null) return ''
// 兼容简单的字符串地址和对象地址
if (typeof addr === 'string') return addr
if (addr.address) return addr.address
return (addr.province || '') + (addr.city || '') + (addr.district || '') + (addr.detail || addr.address_detail || '')
const addrObj = addr as Record<string, any>
if (addrObj['address'] != null) return addrObj['address'] as string
return ((addrObj['province'] as string) ?? '') + ((addrObj['city'] as string) ?? '') + ((addrObj['district'] as string) ?? '') + ((addrObj['detail'] as string) ?? (addrObj['address_detail'] as string) ?? '')
}
const getSpecText = (specs: any) => {
if (!specs) return ''
const getSpecText = (specs: any): string => {
if (specs == null) return ''
if (typeof specs === 'string') return specs
return Object.keys(specs).map(k => `${k}:${specs[k]}`).join(' ')
// 简化处理:直接返回字符串形式
return JSON.stringify(specs)
}
const formatTime = (iso: string) => {
if (!iso) return ''
const formatTime = (iso: string): string => {
if (iso == '') return ''
const d = new Date(iso)
return `${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()} ${d.getHours()}:${d.getMinutes()}`
}
const getPaymentMethodText = (method: any) => {
const getPaymentMethodText = (method: any): string => {
return '在线支付'
}
const copyText = (text: string) => {
if(!text) return
if(text == '') return
uni.setClipboardData({
data: text,
success: () => uni.showToast({ title: '已复制' })
})
}
const loadShopInfo = async (merchantId: string) => {
const result = await supa
.from('ml_shops')
.select('shop_name')
.eq('merchant_id', merchantId)
.single()
const resultObj = result as Record<string, any>
const resultData = resultObj['data']
if (resultData != null) {
const dataObj = resultData as Record<string, any>
shopName.value = dataObj['shop_name'] as string
}
}
const loadOrderDetail = async () => {
uni.showLoading({ title: '加载中' })
try {
const data = await supabaseService.getOrderDetail(orderId.value)
if (data != null) {
const dataObj = data as Record<string, any>
order.value = data as OrderType
const items = dataObj['ml_order_items']
orderItems.value = items != null ? (items as OrderItemType[]) : []
deliveryAddress.value = dataObj['shipping_address'] as AddressType
// 获取店铺信息
const merchantId = dataObj['merchant_id'] as string
if (merchantId != null && merchantId != '') {
loadShopInfo(merchantId)
}
} else {
uni.showToast({ title: '订单不存在', icon: 'none' })
}
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
uni.hideLoading()
}
}
// 动作函数
const contactService = () => {
uni.showActionSheet({
@@ -278,22 +317,31 @@ const contactService = () => {
}
const payOrder = () => {
const totalAmount = order.value?.total_amount ?? 0
uni.navigateTo({
url: `/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${order.value.total_amount}`
url: `/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`
})
}
const doCancelOrder = async () => {
const result = await supa.from('ml_orders').update({ order_status: 5 }).eq('id', orderId.value)
const resultObj = result as Record<string, any>
const resultError = resultObj['error']
if(resultError == null) {
if (order.value != null) {
order.value.order_status = 5
}
uni.showToast({ title: '订单已取消' })
}
}
const cancelOrder = () => {
uni.showModal({
title: '提示',
content: '确定要取消订单吗?',
success: async (res) => {
success: (res) => {
if (res.confirm) {
const { error } = await supa.from('ml_orders').update({ order_status: 5 }).eq('id', orderId.value)
if(!error) {
order.value.order_status = 5
uni.showToast({ title: '订单已取消' })
}
doCancelOrder()
}
}
})
@@ -307,32 +355,39 @@ const viewLogistics = () => {
uni.navigateTo({ url: `/pages/mall/consumer/logistics?orderId=${orderId.value}` })
}
const confirmReceive = async () => {
const goToReview = () => {
uni.navigateTo({ url: `/pages/mall/consumer/review?orderId=${orderId.value}` })
}
const doConfirmReceive = async () => {
const result = await supabaseService.confirmReceipt(orderId.value)
if (result.success) {
if (order.value != null) {
order.value.order_status = 4
}
uni.showToast({ title: '收货成功' })
setTimeout(() => goToReview(), 1500)
} else {
uni.showToast({ title: result.error ?? '失败', icon: 'none' })
}
}
const confirmReceive = () => {
uni.showModal({
title: '确认收货',
content: '确保您已收到货物',
success: async (res) => {
success: (res) => {
if (res.confirm) {
const result = await supabaseService.confirmReceipt(orderId.value)
if (result.success) {
order.value.order_status = 4
uni.showToast({ title: '收货成功' })
setTimeout(() => goToReview(), 1500)
} else {
uni.showToast({ title: result.error || '失败', icon: 'none' })
}
doConfirmReceive()
}
}
})
}
const goToReview = () => {
uni.navigateTo({ url: `/pages/mall/consumer/review?orderId=${orderId.value}` })
}
const rePurchase = async () => {
uni.showLoading({ title: '处理中' })
const success = await supabaseService.rePurchase(order.value)
const orderData = order.value as any
const success = await supabaseService.rePurchase(orderData)
uni.hideLoading()
if (success) {
uni.showToast({ title: '已加入购物车' })
@@ -344,21 +399,27 @@ const rePurchase = async () => {
}
}
const doApplyRefund = async (reason: string) => {
const success = await supabaseService.applyRefund(orderId.value, reason)
if (success) {
if (order.value != null) {
order.value.order_status = 6
}
uni.showToast({ title: '申请已提交' })
} else {
uni.showToast({ title: '提交失败', icon: 'none' })
}
}
const applyRefund = () => {
uni.showModal({
title: '申请退款',
editable: true,
placeholderText: '请输入退款原因',
success: async (res) => {
success: (res) => {
if (res.confirm) {
const reason = res.content || '用户主动申请'
const success = await supabaseService.applyRefund(orderId.value, reason)
if (success) {
order.value.order_status = 6
uni.showToast({ title: '申请已提交' })
} else {
uni.showToast({ title: '提交失败', icon: 'none' })
}
const reason = res.content ?? '用户主动申请'
doApplyRefund(reason)
}
}
})
@@ -379,19 +440,31 @@ const goToProduct = (pid: string) => {
uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${pid}` })
}
// 生命周期 - 在所有函数定义之后
onLoad((options) => {
const id = options['id']
const orderIdParam = options['orderId']
if (id != null && id != '') {
orderId.value = id as string
loadOrderDetail()
} else if (orderIdParam != null && orderIdParam != '') {
orderId.value = orderIdParam as string
loadOrderDetail()
}
})
</script>
<style scoped>
.order-detail-page {
display: flex;
flex-direction: column;
height: 100vh;
flex: 1;
background-color: #f5f5f5;
}
.scroll-content {
flex: 1;
overflow-y: auto;
padding-bottom: 20px;
}
@@ -419,14 +492,12 @@ const goToProduct = (pid: string) => {
.status-text {
font-size: 18px;
font-weight: bold;
display: block;
}
.status-desc {
font-size: 12px;
opacity: 0.9;
margin-top: 5px;
display: block;
}
/* 配送信息 */
@@ -434,7 +505,6 @@ const goToProduct = (pid: string) => {
font-weight: bold;
font-size: 16px;
margin-bottom: 10px;
display: block;
}
.delivery-address {
@@ -535,10 +605,9 @@ const goToProduct = (pid: string) => {
font-size: 14px;
line-height: 1.4;
color: #333;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
lines: 2; /* uvue specific */
}
.product-spec {
@@ -618,7 +687,7 @@ const goToProduct = (pid: string) => {
justify-content: space-between;
align-items: center;
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
padding-bottom: calc(10px + env(safe-area-inset-bottom));
padding-bottom: 30px;
}
.action-left {
@@ -631,7 +700,7 @@ const goToProduct = (pid: string) => {
align-items: center;
font-size: 10px;
color: #666;
background: none;
background-color: transparent;
line-height: 1.2;
}
@@ -642,7 +711,11 @@ const goToProduct = (pid: string) => {
.action-right {
display: flex;
gap: 10px;
}
/* Add margin to buttons inside action-right for spacing */
.action-right .btn {
margin-left: 10px;
}
.btn {
@@ -665,4 +738,4 @@ const goToProduct = (pid: string) => {
/* 状态样式 */
.status-4 .status-text { /* Completed */ }
</style>
</style>