继续完善购物逻辑闭环,consumer模块完成度80%
This commit is contained in:
35
pages.json
35
pages.json
@@ -1,20 +1,32 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/user/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户登录",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/index",
|
||||
"path": "pages/mall/consumer/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/wallet",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的钱包"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户登录",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/category",
|
||||
"style": {
|
||||
@@ -150,6 +162,13 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "服务评价"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/chat",
|
||||
"style": {
|
||||
"navigationBarTitleText": "客服聊天",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
|
||||
@@ -16,8 +16,13 @@
|
||||
</view>
|
||||
<text class="address-text">{{ getFullAddress(item) }}</text>
|
||||
</view>
|
||||
<view class="item-edit" @click.stop="editAddress(item.id)">
|
||||
<text class="edit-icon">📝</text>
|
||||
<view class="item-actions">
|
||||
<view class="action-item" @click.stop="editAddress(item.id)">
|
||||
<text class="action-icon">📝</text>
|
||||
</view>
|
||||
<view class="action-item" @click.stop="deleteAddress(item.id)">
|
||||
<text class="action-icon"><3E>️</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -104,6 +109,27 @@ const addAddress = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 删除地址
|
||||
const deleteAddress = (id: string) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该地址吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const index = addresses.value.findIndex(addr => addr.id === id)
|
||||
if (index !== -1) {
|
||||
addresses.value.splice(index, 1)
|
||||
uni.setStorageSync('addresses', JSON.stringify(addresses.value))
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const editAddress = (id: string) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/address-edit?id=${id}`
|
||||
@@ -130,6 +156,26 @@ const selectAddress = (item: Address) => {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 响应式布局优化 */
|
||||
@media screen and (min-width: 768px) {
|
||||
.address-list {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.address-list-page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.footer-btn {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.address-list-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
@@ -216,12 +262,24 @@ const selectAddress = (item: Address) => {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.item-edit {
|
||||
.item-actions {
|
||||
padding: 10px;
|
||||
border-left: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: column; /* 竖向排列图标 */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
.action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@@ -127,6 +127,8 @@
|
||||
<text class="total-text">合计:</text>
|
||||
<text class="total-price">¥{{ totalPrice }}</text>
|
||||
</view>
|
||||
<!-- 结算按钮:即使在管理模式下,如果用户想结算也可以(或者在管理模式下隐藏结算,只显示删除) -->
|
||||
<!-- 需求:加购商品前面的按钮时;cart购物车页面顶部的管理点击能对加购商品进行批量删除 -->
|
||||
<button v-if="!isManageMode" class="checkout-btn" @click="goToCheckout">
|
||||
去结算({{ selectedCount }})
|
||||
</button>
|
||||
@@ -154,38 +156,58 @@ const mockRecommendProducts = [
|
||||
{
|
||||
id: 'rec_001',
|
||||
shopId: 'shop_rec_1',
|
||||
shopName: '极速运动专营店',
|
||||
shopName: '潮流运动旗舰店',
|
||||
name: '运动保温杯',
|
||||
price: 59,
|
||||
image: 'https://picsum.photos/100/100?random=11',
|
||||
specification: '黑色 500ml'
|
||||
specification: '颜色:星空黑 | 容量:500ml | 材质:304不锈钢',
|
||||
specDetails: {
|
||||
color: '星空黑',
|
||||
capacity: '500ml',
|
||||
material: '304不锈钢'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rec_002',
|
||||
shopId: 'shop_rec_2',
|
||||
shopName: '品质生活居家',
|
||||
shopName: '智能家居生活馆',
|
||||
name: '声波电动牙刷',
|
||||
price: 129,
|
||||
image: 'https://picsum.photos/100/100?random=12',
|
||||
specification: '象牙白 配刷头'
|
||||
specification: '颜色:珍珠白 | 刷头:敏感型×2 | 续航:30天',
|
||||
specDetails: {
|
||||
color: '珍珠白',
|
||||
brushHead: '敏感型×2',
|
||||
batteryLife: '30天'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rec_003',
|
||||
shopId: 'shop_rec_3',
|
||||
shopName: '健康护理大药房',
|
||||
shopName: '健康防护专家店',
|
||||
name: '医用护理口罩',
|
||||
price: 29.9,
|
||||
image: 'https://picsum.photos/100/100?random=13',
|
||||
specification: '独立包装 50只'
|
||||
specification: '规格:三层防护 | 数量:50只独立装 | 执行标准:YY0469',
|
||||
specDetails: {
|
||||
layers: '三层防护',
|
||||
quantity: '50只',
|
||||
standard: 'YY0469'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rec_004',
|
||||
shopId: 'shop_rec_1',
|
||||
shopName: '极速运动专营店',
|
||||
shopId: 'shop_rec_4',
|
||||
shopName: '户外运动装备店',
|
||||
name: '专业护膝',
|
||||
price: 45,
|
||||
image: 'https://picsum.photos/100/100?random=14',
|
||||
specification: 'L码 单只装'
|
||||
specification: '尺码:L码 | 材质:记忆棉+弹力布 | 适用:篮球/跑步',
|
||||
specDetails: {
|
||||
size: 'L码',
|
||||
material: '记忆棉+弹力布',
|
||||
suitableFor: '篮球/跑步'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -442,7 +464,31 @@ const goToCheckout = () => {
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/checkout' })
|
||||
|
||||
// 获取选中的商品
|
||||
const selectedItems = cartItems.value
|
||||
.filter(item => item.selected)
|
||||
.map(item => ({
|
||||
id: item.id,
|
||||
product_id: item.id, // 使用商品ID作为product_id
|
||||
sku_id: item.id, // 使用商品ID作为sku_id
|
||||
product_name: item.name,
|
||||
product_image: item.image,
|
||||
sku_specifications: item.spec,
|
||||
price: item.price,
|
||||
quantity: item.quantity
|
||||
}))
|
||||
|
||||
// 跳转到结算页面并传递数据
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/checkout',
|
||||
success: (res) => {
|
||||
// 通过eventChannel传递数据
|
||||
res.eventChannel.emit('acceptData', {
|
||||
selectedItems: selectedItems
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -792,17 +838,17 @@ const goToCheckout = () => {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.recommend-name {
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
line-height: 1.4;
|
||||
height: 36px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.recommend-name {
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
line-height: 1.4;
|
||||
height: 36px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.recommend-bottom {
|
||||
display: flex;
|
||||
@@ -1092,4 +1138,4 @@ const goToCheckout = () => {
|
||||
padding: 8px 25px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -702,7 +702,7 @@ const navigateToSearch = () => uni.navigateTo({ url: '/pages/mall/consumer/searc
|
||||
const navigateToCart = () => uni.navigateTo({ url: '/pages/medicine/cart' })
|
||||
const navigateToProduct = (product: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/product-detail?productId=${product.id}`
|
||||
url: `/pages/mall/consumer/product-detail?productId=${product.id}&price=${product.price}&originalPrice=${product.originalPrice || ''}`
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -22,17 +22,26 @@
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<view class="products-section">
|
||||
<view v-for="item in checkoutItems" :key="item.id" class="product-item">
|
||||
<image class="product-image" :src="item.product_image" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ item.product_name }}</text>
|
||||
<text v-if="item.sku_specifications" class="product-spec">{{ getSpecText(item.sku_specifications) }}</text>
|
||||
<view class="product-bottom">
|
||||
<text class="product-price">¥{{ item.price }}</text>
|
||||
<text class="product-quantity">×{{ item.quantity }}</text>
|
||||
<!-- 调试信息 -->
|
||||
<view v-if="checkoutItems.length > 0" class="debug-info">
|
||||
<text class="debug-text">调试:共{{ checkoutItems.length }}件商品,总价计算:{{ totalAmount }}</text>
|
||||
</view>
|
||||
<view v-if="checkoutItems.length > 0">
|
||||
<view v-for="item in checkoutItems" :key="item.id" class="product-item">
|
||||
<image class="product-image" :src="item.product_image" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ item.product_name }}</text>
|
||||
<text v-if="item.sku_specifications" class="product-spec">{{ getSpecText(item.sku_specifications) }}</text>
|
||||
<view class="product-bottom">
|
||||
<text class="product-price">¥{{ item.price }}</text>
|
||||
<text class="product-quantity">×{{ item.quantity }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="no-products">
|
||||
<text class="no-products-text">暂无商品信息</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 配送方式 -->
|
||||
@@ -210,11 +219,28 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 确认保存弹窗 -->
|
||||
<view v-if="showSaveConfirm" class="confirm-popup-mask">
|
||||
<view class="confirm-popup">
|
||||
<view class="confirm-header">
|
||||
<text class="confirm-title">保存地址</text>
|
||||
</view>
|
||||
<view class="confirm-content">
|
||||
<text class="confirm-message">是否保存该地址用于下次使用?</text>
|
||||
</view>
|
||||
<view class="confirm-buttons">
|
||||
<button class="confirm-btn cancel" @click="handleSaveConfirm(false)">仅本次</button>
|
||||
<button class="confirm-btn confirm" @click="handleSaveConfirm(true)">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ref, onMounted, computed, watch, onUnmounted, getCurrentInstance } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
|
||||
type CheckoutItemType = {
|
||||
id: string
|
||||
@@ -267,10 +293,44 @@ const showNewAddressForm = ref<boolean>(false)
|
||||
const showSaveConfirm = ref<boolean>(false)
|
||||
const smartAddressInput = ref<string>('')
|
||||
|
||||
// 计算属性
|
||||
// 计算属性 - 修复价格同步问题
|
||||
const totalAmount = computed(() => {
|
||||
return checkoutItems.value.reduce((sum, item) =>
|
||||
sum + (item.price * item.quantity), 0)
|
||||
console.log('计算商品总价,checkoutItems:', checkoutItems.value)
|
||||
if (!checkoutItems.value || checkoutItems.value.length === 0) {
|
||||
console.log('商品列表为空,返回0')
|
||||
return 0
|
||||
}
|
||||
|
||||
// 确保每个商品的价格和数量都是数字类型,并计算总和
|
||||
const total = checkoutItems.value.reduce((sum, item) => {
|
||||
// 确保item存在且包含必要的属性
|
||||
if (!item) return sum
|
||||
|
||||
// 将价格转换为数字(确保是数字类型)
|
||||
let price = 0
|
||||
if (item.price !== undefined && item.price !== null) {
|
||||
price = typeof item.price === 'string' ? parseFloat(item.price) : Number(item.price)
|
||||
}
|
||||
|
||||
// 将数量转换为数字
|
||||
let quantity = 0
|
||||
if (item.quantity !== undefined && item.quantity !== null) {
|
||||
quantity = typeof item.quantity === 'string' ? parseInt(item.quantity) : Number(item.quantity)
|
||||
}
|
||||
|
||||
// 验证转换后的数字是否有效
|
||||
if (isNaN(price) || isNaN(quantity) || price <= 0 || quantity <= 0) {
|
||||
console.warn('商品价格或数量无效:', item, 'price:', price, 'quantity:', quantity)
|
||||
return sum
|
||||
}
|
||||
|
||||
const itemTotal = price * quantity
|
||||
console.log(`商品 ${item.product_name},单价: ${price},数量: ${quantity},小计: ${itemTotal}`)
|
||||
return sum + itemTotal
|
||||
}, 0)
|
||||
|
||||
console.log('商品总价计算结果:', total)
|
||||
return total
|
||||
})
|
||||
|
||||
const deliveryFee = computed(() => {
|
||||
@@ -282,6 +342,7 @@ const discountAmount = computed(() => {
|
||||
if (!selectedCoupon.value || !selectedCoupon.value.template) return 0
|
||||
|
||||
const coupon = selectedCoupon.value.template
|
||||
// 确保使用计算后的商品总价进行比较
|
||||
if (totalAmount.value < coupon.min_order_amount) return 0
|
||||
|
||||
// 简单处理:假设都是满减券
|
||||
@@ -289,14 +350,105 @@ const discountAmount = computed(() => {
|
||||
})
|
||||
|
||||
const actualAmount = computed(() => {
|
||||
let amount = totalAmount.value + deliveryFee.value - discountAmount.value
|
||||
// 确保所有值都是数字类型
|
||||
const total = typeof totalAmount.value === 'number' ? totalAmount.value : 0
|
||||
const delivery = typeof deliveryFee.value === 'number' ? deliveryFee.value : 0
|
||||
const discount = typeof discountAmount.value === 'number' ? discountAmount.value : 0
|
||||
|
||||
// 正确计算:商品总价 + 运费 - 优惠减免
|
||||
let amount = total + delivery - discount
|
||||
|
||||
// 金额必须大于等于0
|
||||
return amount > 0 ? amount : 0
|
||||
})
|
||||
|
||||
// 监听checkoutItems变化 - 调试用
|
||||
watch(checkoutItems, (newItems) => {
|
||||
console.log('checkoutItems变化了:', newItems)
|
||||
console.log('商品总价计算:', totalAmount.value)
|
||||
}, { deep: true })
|
||||
|
||||
// 页面加载时监听eventChannel
|
||||
onLoad(() => {
|
||||
// 优先检查Storage中是否有"立即购买"的数据
|
||||
const checkoutType = uni.getStorageSync('checkout_type')
|
||||
if (checkoutType === 'buy_now') {
|
||||
console.log('检测到立即购买模式,从Storage加载数据')
|
||||
const itemsStr = uni.getStorageSync('checkout_items')
|
||||
if (itemsStr) {
|
||||
try {
|
||||
const items = JSON.parse(itemsStr as string)
|
||||
console.log('从Storage加载的商品数据:', items)
|
||||
processCheckoutItems(items)
|
||||
|
||||
// 清除Storage,避免污染下次进入(刷新页面时可能需要保留?暂时不清除,或者在离开页面时清除)
|
||||
// uni.removeStorageSync('checkout_type')
|
||||
// uni.removeStorageSync('checkout_items')
|
||||
loadDefaultAddress()
|
||||
return // 成功加载,直接返回
|
||||
} catch (e) {
|
||||
console.error('解析立即购买数据失败', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从上一页获取数据
|
||||
const eventChannel = uni.getEventChannel ? uni.getEventChannel() : null
|
||||
if (eventChannel) {
|
||||
eventChannel.on('acceptData', (data: any) => {
|
||||
console.log('接收到商品数据:', data)
|
||||
let items = data.selectedItems || []
|
||||
processCheckoutItems(items)
|
||||
loadDefaultAddress()
|
||||
})
|
||||
} else {
|
||||
// 如果没有eventChannel,尝试从本地存储加载(例如从购物车进入)
|
||||
loadFromLocalStorage()
|
||||
}
|
||||
})
|
||||
|
||||
// 处理商品数据清洗
|
||||
const processCheckoutItems = (items: any[]) => {
|
||||
// 数据清洗:确保价格和数量是数字类型
|
||||
if (items && items.length > 0) {
|
||||
items = items.map((item: any) => {
|
||||
// 确保价格是数字
|
||||
let price = item.price
|
||||
if (price !== undefined && price !== null) {
|
||||
price = typeof price === 'string' ? parseFloat(price) : Number(item.price)
|
||||
if (isNaN(price)) price = 0
|
||||
} else {
|
||||
price = 0
|
||||
}
|
||||
|
||||
// 确保数量是数字
|
||||
let quantity = item.quantity
|
||||
if (quantity !== undefined && quantity !== null) {
|
||||
quantity = typeof quantity === 'string' ? parseInt(quantity) : Number(item.quantity)
|
||||
if (isNaN(quantity) || quantity < 1) quantity = 1
|
||||
} else {
|
||||
quantity = 1
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
price: Number(price.toFixed(2)), // 保留两位小数
|
||||
quantity: quantity
|
||||
}
|
||||
})
|
||||
}
|
||||
checkoutItems.value = items
|
||||
// 调试:打印每个商品的价格
|
||||
if (checkoutItems.value && checkoutItems.value.length > 0) {
|
||||
console.log('清洗后商品价格明细:')
|
||||
checkoutItems.value.forEach((item: any, index: number) => {
|
||||
console.log(`商品${index}:`, item.product_name, '价格:', item.price, '类型:', typeof item.price, '数量:', item.quantity)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadCheckoutData()
|
||||
|
||||
// 监听地址更新事件
|
||||
uni.$on('addressUpdated', (updatedAddressList: any) => {
|
||||
addressList.value = updatedAddressList
|
||||
@@ -316,16 +468,40 @@ onUnmounted(() => {
|
||||
uni.$off('addressUpdated')
|
||||
})
|
||||
|
||||
// 加载结算数据
|
||||
const loadCheckoutData = () => {
|
||||
// 从上一页获取数据
|
||||
const eventChannel = uni.getEventChannel()
|
||||
if (eventChannel) {
|
||||
eventChannel.on('acceptData', (data: any) => {
|
||||
checkoutItems.value = data.selectedItems || []
|
||||
loadDefaultAddress()
|
||||
})
|
||||
// 从本地存储加载结算数据(例如从购物车进入)
|
||||
const loadFromLocalStorage = () => {
|
||||
// 尝试从本地存储获取购物车选中的商品
|
||||
const cartData = uni.getStorageSync('cart')
|
||||
if (cartData) {
|
||||
try {
|
||||
const cartItems = JSON.parse(cartData as string) as any[]
|
||||
// 筛选选中的商品
|
||||
const selectedCartItems = cartItems.filter(item => item.selected === true)
|
||||
if (selectedCartItems.length > 0) {
|
||||
// 转换为CheckoutItemType格式
|
||||
const convertedItems: CheckoutItemType[] = selectedCartItems.map(item => ({
|
||||
id: item.id,
|
||||
product_id: item.productId,
|
||||
sku_id: item.id, // 购物车中item.id就是SKU ID
|
||||
product_name: item.name,
|
||||
product_image: item.image,
|
||||
sku_specifications: item.spec ? { spec: item.spec } : {},
|
||||
price: item.price,
|
||||
quantity: item.quantity
|
||||
}))
|
||||
checkoutItems.value = convertedItems
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析购物车数据失败:', e)
|
||||
}
|
||||
}
|
||||
loadDefaultAddress()
|
||||
}
|
||||
|
||||
// 加载结算数据(兼容旧版本,现在主要在onLoad中处理)
|
||||
const loadCheckoutData = () => {
|
||||
// 为了兼容性保留,但主要逻辑已在onLoad中实现
|
||||
loadFromLocalStorage()
|
||||
}
|
||||
|
||||
// 加载默认地址
|
||||
@@ -459,7 +635,7 @@ const saveNewAddress = async () => {
|
||||
}
|
||||
|
||||
const userId = getCurrentUserId()
|
||||
if (!userId) return
|
||||
// if (!userId) return // 允许未登录用户保存地址用于演示
|
||||
|
||||
// 触发保存确认弹窗
|
||||
showSaveConfirm.value = true
|
||||
@@ -722,106 +898,52 @@ const submitOrder = async () => {
|
||||
})
|
||||
return
|
||||
}
|
||||
// 模拟提交成功跳转
|
||||
// 实际项目中应等待API返回
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/payment?orderId=ORDER_MOCK_${Date.now()}&amount=${actualAmount.value}`
|
||||
})
|
||||
return;
|
||||
|
||||
const userId = getCurrentUserId()
|
||||
if (!userId) {
|
||||
uni.showToast({
|
||||
title: '用户信息错误',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 生成订单号
|
||||
const orderNo = generateOrderNo()
|
||||
|
||||
const orderData = {
|
||||
order_no: orderNo,
|
||||
user_id: userId,
|
||||
merchant_id: 'default', // 这里需要根据商品确定商家
|
||||
status: 1, // 待支付
|
||||
total_amount: totalAmount.value,
|
||||
discount_amount: discountAmount.value,
|
||||
delivery_fee: deliveryFee.value,
|
||||
actual_amount: actualAmount.value,
|
||||
payment_method: 0, // 待选择
|
||||
payment_status: 0,
|
||||
delivery_address: selectedAddress.value,
|
||||
remark: remark.value,
|
||||
created_at: new Date().toISOString()
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建订单
|
||||
/* const { data: order, error: orderError } = await supa
|
||||
.from('orders')
|
||||
.insert(orderData)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (orderError !== null) {
|
||||
throw orderError
|
||||
}
|
||||
|
||||
// 创建订单商品项
|
||||
const orderItems = checkoutItems.value.map(item => ({
|
||||
order_id: order.id,
|
||||
product_id: item.product_id,
|
||||
sku_id: item.sku_id,
|
||||
product_name: item.product_name,
|
||||
sku_specifications: item.sku_specifications,
|
||||
price: item.price,
|
||||
quantity: item.quantity,
|
||||
total_amount: item.price * item.quantity
|
||||
}))
|
||||
|
||||
const { error: itemsError } = await supa
|
||||
.from('order_items')
|
||||
.insert(orderItems)
|
||||
|
||||
if (itemsError !== null) {
|
||||
throw itemsError
|
||||
}
|
||||
|
||||
// 使用优惠券
|
||||
if (selectedCoupon.value) {
|
||||
const { error: couponError } = await supa
|
||||
.from('user_coupons')
|
||||
.update({
|
||||
status: 2, // 已使用
|
||||
used_at: new Date().toISOString()
|
||||
})
|
||||
.eq('id', selectedCoupon.value.id)
|
||||
|
||||
if (couponError !== null) {
|
||||
console.error('更新优惠券状态失败:', couponError)
|
||||
}
|
||||
}
|
||||
|
||||
// 清空购物车
|
||||
await clearShoppingCart()
|
||||
|
||||
// 跳转到支付页面
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/payment?orderId=${order.id}&amount=${actualAmount.value}`
|
||||
}) */
|
||||
// MOCK ORDER SUBMISSION
|
||||
// 模拟创建成功
|
||||
try {
|
||||
const mockOrderId = `order_${Date.now()}`
|
||||
|
||||
// MOCK ORDER SUBMISSION
|
||||
// 模拟创建成功
|
||||
const mockOrderId = `order_mock_${Date.now()}`
|
||||
// 创建订单对象
|
||||
const newOrder = {
|
||||
id: mockOrderId,
|
||||
order_no: generateOrderNo(),
|
||||
user_id: getCurrentUserId() || 'user_001',
|
||||
merchant_id: checkoutItems.value[0]?.product_id || 'merchant_001', // 简化处理,取第一个商品的merchant
|
||||
status: 1, // 待支付
|
||||
total_amount: totalAmount.value,
|
||||
discount_amount: discountAmount.value,
|
||||
delivery_fee: deliveryFee.value,
|
||||
actual_amount: actualAmount.value,
|
||||
payment_method: 0,
|
||||
payment_status: 0,
|
||||
delivery_address: selectedAddress.value,
|
||||
items: checkoutItems.value,
|
||||
created_at: new Date().toISOString()
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
const storedOrders = uni.getStorageSync('orders')
|
||||
let orders: any[] = []
|
||||
if (storedOrders) {
|
||||
try {
|
||||
orders = JSON.parse(storedOrders as string) as any[]
|
||||
} catch (e) {
|
||||
console.error('解析订单数据失败', e)
|
||||
}
|
||||
}
|
||||
orders.unshift(newOrder)
|
||||
uni.setStorageSync('orders', JSON.stringify(orders))
|
||||
|
||||
uni.showLoading({ title: '提交中...' })
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
uni.hideLoading()
|
||||
|
||||
// 携带价格详情跳转
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/payment?orderId=${mockOrderId}&amount=${actualAmount.value}`
|
||||
url: `/pages/mall/consumer/payment?orderId=${mockOrderId}&amount=${actualAmount.value}&productAmount=${totalAmount.value}&deliveryFee=${deliveryFee.value}&discountAmount=${discountAmount.value}`
|
||||
})
|
||||
|
||||
} catch (err) {
|
||||
} catch (err) {
|
||||
console.error('创建订单失败:', err)
|
||||
uni.showToast({
|
||||
title: '订单创建失败',
|
||||
@@ -967,6 +1089,20 @@ const goBack = () => {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.debug-info {
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.debug-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
display: flex;
|
||||
padding: 15px 0;
|
||||
@@ -1505,4 +1641,76 @@ const goBack = () => {
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
}
|
||||
/* 确认弹窗样式 */
|
||||
.confirm-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10002;
|
||||
}
|
||||
|
||||
.confirm-popup {
|
||||
background-color: #ffffff;
|
||||
width: 80%;
|
||||
max-width: 320px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.confirm-header {
|
||||
padding: 20px 0 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.confirm-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.confirm-content {
|
||||
padding: 0 20px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.confirm-message {
|
||||
font-size: 16px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.confirm-buttons {
|
||||
display: flex;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.confirm-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.confirm-btn.cancel {
|
||||
color: #666666;
|
||||
border-right: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.confirm-btn.confirm {
|
||||
color: #007aff;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -111,9 +111,9 @@ const goShopping = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const goToDetail = (id: string) => {
|
||||
const goToDetail = (product: Product) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/product-detail?productId=${id}`
|
||||
url: `/pages/mall/consumer/product-detail?productId=${product.id}&price=${product.price}&originalPrice=${product.original_price || ''}`
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ const viewProduct = (item: FootprintType) => {
|
||||
if (isEditMode.value) return
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/product-detail?productId=${item.id}`
|
||||
url: `/pages/mall/consumer/product-detail?productId=${item.id}&price=${item.price}&originalPrice=${item.original_price || ''}`
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -788,7 +788,7 @@ const navigateToSearch = () => uni.navigateTo({ url: '/pages/mall/consumer/searc
|
||||
const navigateToNews = () => uni.navigateTo({ url: '/pages/news/list' })
|
||||
const navigateToProduct = (product: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/product-detail?productId=${product.id}`
|
||||
url: `/pages/mall/consumer/product-detail?productId=${product.id}&price=${product.price}&originalPrice=${product.originalPrice || ''}`
|
||||
})
|
||||
}
|
||||
const navigateToCategory = (item: any) => {
|
||||
|
||||
@@ -142,82 +142,83 @@ export default {
|
||||
}
|
||||
},
|
||||
onLoad(options: any) {
|
||||
const orderId = options.orderId as string
|
||||
const orderId = options.id || options.orderId as string
|
||||
if (orderId) {
|
||||
this.loadOrderDetail(orderId)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadOrderDetail(orderId: string) {
|
||||
// 模拟加载订单详情数据
|
||||
this.order = {
|
||||
id: orderId,
|
||||
order_no: 'ORD202401150001',
|
||||
user_id: 'user_001',
|
||||
merchant_id: 'merchant_001',
|
||||
status: 3, // 1:待支付 2:待发货 3:待收货 4:已完成 5:已取消
|
||||
total_amount: 299.98,
|
||||
discount_amount: 30.00,
|
||||
delivery_fee: 8.00,
|
||||
actual_amount: 277.98,
|
||||
payment_method: 1, // 1:微信支付 2:支付宝 3:余额
|
||||
payment_status: 1,
|
||||
delivery_address: {
|
||||
name: '张三',
|
||||
phone: '13800138000',
|
||||
detail: '北京市朝阳区某某街道某某小区1号楼101室'
|
||||
},
|
||||
created_at: '2024-01-15 14:30:00'
|
||||
// 尝试从本地存储加载订单
|
||||
const ordersStr = uni.getStorageSync('orders')
|
||||
let localOrder: any = null
|
||||
|
||||
if (ordersStr) {
|
||||
const orders = JSON.parse(ordersStr as string) as any[]
|
||||
localOrder = orders.find((o: any) => o.id === orderId)
|
||||
}
|
||||
|
||||
this.orderItems = [
|
||||
{
|
||||
id: 'item_001',
|
||||
order_id: orderId,
|
||||
product_id: 'product_001',
|
||||
sku_id: 'sku_001',
|
||||
product_name: '精选好物商品',
|
||||
sku_specifications: { color: '红色', size: 'M' },
|
||||
price: 199.99,
|
||||
quantity: 1,
|
||||
total_amount: 199.99,
|
||||
product_image: '/static/product1.jpg'
|
||||
},
|
||||
{
|
||||
id: 'item_002',
|
||||
order_id: orderId,
|
||||
product_id: 'product_002',
|
||||
sku_id: 'sku_002',
|
||||
product_name: '优质配件',
|
||||
sku_specifications: { type: '标准版' },
|
||||
price: 99.99,
|
||||
quantity: 1,
|
||||
total_amount: 99.99,
|
||||
product_image: '/static/product2.jpg'
|
||||
}
|
||||
]
|
||||
|
||||
this.merchant = {
|
||||
id: 'merchant_001',
|
||||
user_id: 'user_001',
|
||||
shop_name: '优质好店',
|
||||
shop_logo: '/static/shop-logo.png',
|
||||
shop_banner: '/static/shop-banner.png',
|
||||
shop_description: '专注品质生活',
|
||||
contact_name: '店主小王',
|
||||
contact_phone: '13800138000',
|
||||
shop_status: 1,
|
||||
rating: 4.8,
|
||||
total_sales: 15680,
|
||||
created_at: '2023-06-01'
|
||||
}
|
||||
|
||||
if (this.order.status >= 3) {
|
||||
this.deliveryInfo = {
|
||||
courier_name: '李师傅',
|
||||
courier_phone: '13900139000',
|
||||
tracking_no: 'YT123456789'
|
||||
}
|
||||
|
||||
if (localOrder) {
|
||||
// 使用本地存储的数据
|
||||
this.order = localOrder
|
||||
|
||||
// 处理商品项
|
||||
if (localOrder.items) {
|
||||
this.orderItems = localOrder.items.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
product_image: item.product_image || item.image || '/static/default-product.png',
|
||||
product_name: item.product_name || item.name,
|
||||
sku_specifications: item.sku_specifications || item.specifications
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理商家信息(模拟,因为本地订单可能没有完整的商家信息)
|
||||
this.merchant = {
|
||||
id: localOrder.merchant_id || 'merchant_001',
|
||||
user_id: 'user_001',
|
||||
shop_name: localOrder.shopName || '优质好店',
|
||||
shop_logo: '/static/shop-logo.png',
|
||||
shop_banner: '/static/shop-banner.png',
|
||||
shop_description: '专注品质生活',
|
||||
contact_name: '店主小王',
|
||||
contact_phone: '13800138000',
|
||||
shop_status: 1,
|
||||
rating: 4.8,
|
||||
total_sales: 15680,
|
||||
created_at: '2023-06-01'
|
||||
}
|
||||
|
||||
if (this.order.status >= 3) {
|
||||
this.deliveryInfo = {
|
||||
courier_name: '李师傅',
|
||||
courier_phone: '13900139000',
|
||||
tracking_no: 'YT123456789'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 回退到模拟数据
|
||||
this.order = {
|
||||
id: orderId,
|
||||
order_no: 'ORD202401150001',
|
||||
user_id: 'user_001',
|
||||
merchant_id: 'merchant_001',
|
||||
status: 3, // 1:待支付 2:待发货 3:待收货 4:已完成 5:已取消
|
||||
total_amount: 299.98,
|
||||
discount_amount: 30.00,
|
||||
delivery_fee: 8.00,
|
||||
actual_amount: 277.98,
|
||||
payment_method: 1, // 1:微信支付 2:支付宝 3:余额
|
||||
payment_status: 1,
|
||||
delivery_address: {
|
||||
name: '张三',
|
||||
phone: '13800138000',
|
||||
detail: '北京市朝阳区某某街道某某小区1号楼101室'
|
||||
},
|
||||
created_at: '2024-01-15 14:30:00'
|
||||
}
|
||||
// ... (原有模拟数据逻辑保留)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
<!-- pages/mall/consumer/orders.uvue -->
|
||||
<template>
|
||||
<view class="orders-page">
|
||||
<!-- 顶部标题栏 -->
|
||||
<view class="orders-header">
|
||||
<text class="header-title">我的订单</text>
|
||||
<view class="header-actions">
|
||||
<text class="search-icon" @click="navigateToSearch">🔍</text>
|
||||
</view>
|
||||
<!-- 顶部标题栏 -->
|
||||
<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">
|
||||
@@ -158,6 +166,7 @@ const hasMore = ref<boolean>(true)
|
||||
const refreshing = ref<boolean>(false)
|
||||
const page = ref<number>(1)
|
||||
const activeTab = ref<string>('all')
|
||||
const searchKeyword = ref<string>('')
|
||||
|
||||
// 订单标签页
|
||||
const orderTabs = reactive([
|
||||
@@ -350,45 +359,66 @@ const loadOrders = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Mock Data Loading
|
||||
await new Promise(resolve => setTimeout(resolve, 500)) // Simulate network delay
|
||||
|
||||
// Use filtered mock orders based on user ID logic if needed, but for now just return all mock orders
|
||||
// In a real app we'd filter by userId
|
||||
|
||||
/* const { data, error } = await supa
|
||||
.from('orders')
|
||||
.select('*, order_items(*)')
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false })
|
||||
|
||||
if (error != null) {
|
||||
console.error('加载订单失败:', error)
|
||||
return
|
||||
// 从本地存储获取订单
|
||||
const ordersStr = uni.getStorageSync('orders')
|
||||
let localOrders: any[] = []
|
||||
if (ordersStr) {
|
||||
localOrders = JSON.parse(ordersStr as string) as any[]
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
orders.value = data.map((order: any) => ({
|
||||
id: order.id,
|
||||
order_no: order.order_no,
|
||||
status: order.status,
|
||||
create_time: formatDate(order.created_at),
|
||||
product_amount: order.total_amount,
|
||||
shipping_fee: order.delivery_fee,
|
||||
total_amount: order.actual_amount,
|
||||
products: (order.order_items as any[]).map((item: any) => ({
|
||||
id: item.product_id,
|
||||
name: item.product_name,
|
||||
price: item.price,
|
||||
image: '/static/products/1.jpg', // 默认图片
|
||||
spec: formatSpec(item.sku_specifications),
|
||||
quantity: item.quantity
|
||||
}))
|
||||
}))
|
||||
} */
|
||||
// 过滤当前用户的订单
|
||||
// const userOrders = localOrders.filter((o: any) => o.user_id === userId)
|
||||
// 暂时显示所有订单用于测试
|
||||
let userOrders = localOrders
|
||||
|
||||
// Using Mock Data
|
||||
orders.value = mockOrders
|
||||
// 根据标签页过滤
|
||||
let filtered = userOrders
|
||||
const statusMap: Record<string, number> = {
|
||||
'pending': 1,
|
||||
'shipping': 2,
|
||||
'delivering': 3,
|
||||
'completed': 4,
|
||||
'cancelled': 5
|
||||
}
|
||||
|
||||
if (activeTab.value !== 'all') {
|
||||
const targetStatus = statusMap[activeTab.value]
|
||||
filtered = userOrders.filter((o: any) => o.status === targetStatus)
|
||||
}
|
||||
|
||||
// 按时间倒序
|
||||
filtered.sort((a: any, b: any) => {
|
||||
const timeA = new Date(a.created_at || a.create_time).getTime()
|
||||
const timeB = new Date(b.created_at || b.create_time).getTime()
|
||||
return timeB - timeA
|
||||
})
|
||||
|
||||
// 处理数据格式以适配当前页面
|
||||
orders.value = filtered.map((order: any) => ({
|
||||
id: order.id,
|
||||
order_no: order.order_no,
|
||||
status: order.status,
|
||||
create_time: order.created_at || order.create_time,
|
||||
product_amount: order.total_amount,
|
||||
shipping_fee: order.delivery_fee,
|
||||
total_amount: order.actual_amount,
|
||||
products: (order.items || order.products || []).map((item: any) => ({
|
||||
id: item.product_id || item.id,
|
||||
name: item.product_name || item.name,
|
||||
price: item.price,
|
||||
image: item.product_image || item.image || '/static/default-product.png',
|
||||
spec: item.sku_specifications ? formatSpec(item.sku_specifications) : (item.spec || ''),
|
||||
quantity: item.quantity
|
||||
}))
|
||||
}))
|
||||
|
||||
// 更新统计数据
|
||||
orderTabs[0].count = userOrders.length
|
||||
orderTabs[1].count = userOrders.filter((o: any) => o.status === 1).length
|
||||
orderTabs[2].count = userOrders.filter((o: any) => o.status === 2).length
|
||||
orderTabs[3].count = userOrders.filter((o: any) => o.status === 3).length
|
||||
orderTabs[4].count = userOrders.filter((o: any) => o.status === 4).length
|
||||
orderTabs[5].count = userOrders.filter((o: any) => o.status === 5).length
|
||||
|
||||
} catch (err) {
|
||||
console.error('加载订单异常:', err)
|
||||
@@ -403,6 +433,55 @@ const formatDate = (isoString: string): string => {
|
||||
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')}`
|
||||
}
|
||||
|
||||
// 搜索相关函数
|
||||
const onSearchInput = (e: any) => {
|
||||
searchKeyword.value = e.detail.value
|
||||
performSearch()
|
||||
}
|
||||
|
||||
const onSearchConfirm = () => {
|
||||
performSearch()
|
||||
}
|
||||
|
||||
const clearSearch = () => {
|
||||
searchKeyword.value = ''
|
||||
performSearch()
|
||||
}
|
||||
|
||||
const performSearch = () => {
|
||||
const keyword = searchKeyword.value.trim().toLowerCase()
|
||||
if (!keyword) {
|
||||
loadOrders()
|
||||
return
|
||||
}
|
||||
|
||||
// 在当前订单数据中搜索
|
||||
const allOrders = getCurrentOrderData() // 这里需要获取完整的订单数据
|
||||
const filtered = allOrders.filter((order: any) => {
|
||||
// 搜索订单号
|
||||
if (order.order_no && order.order_no.toLowerCase().includes(keyword)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 搜索商品名称
|
||||
if (order.products && Array.isArray(order.products)) {
|
||||
return order.products.some((product: any) => {
|
||||
return product.name && product.name.toLowerCase().includes(keyword)
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
orders.value = filtered
|
||||
}
|
||||
|
||||
const getCurrentOrderData = () => {
|
||||
// 这里应该从本地存储或API获取完整订单数据
|
||||
// 暂时返回当前orders.value
|
||||
return orders.value
|
||||
}
|
||||
|
||||
const formatSpec = (specs: any): string => {
|
||||
if (!specs) return ''
|
||||
if (typeof specs === 'object') {
|
||||
@@ -586,54 +665,95 @@ const goShopping = () => {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
.header-search.full-width {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-actions .search-icon {
|
||||
.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 {
|
||||
outline: none;
|
||||
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: #666;
|
||||
color: #999;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background-color: #ddd;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 标签页 */
|
||||
.order-tabs {
|
||||
background-color: white;
|
||||
border-bottom: 1px solid #eee;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tab-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 15px;
|
||||
height: 100%;
|
||||
padding: 0 10px;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex-shrink: 0;
|
||||
padding: 0 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 移除 flex: 1,改为自适应宽度或固定最小宽度 */
|
||||
padding: 15px 15px; /* 增加水平内边距 */
|
||||
text-align: center;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: nowrap; /* 防止文字换行 */
|
||||
flex-shrink: 0; /* 防止被压缩 */
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
@@ -971,4 +1091,4 @@ const goShopping = () => {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
996
pages/mall/consumer/ordersgood.uvue
Normal file
996
pages/mall/consumer/ordersgood.uvue
Normal file
@@ -0,0 +1,996 @@
|
||||
<!-- pages/mall/consumer/orders.uvue -->
|
||||
<template>
|
||||
<view class="orders-page">
|
||||
<!-- 顶部标题栏 -->
|
||||
<view class="orders-header">
|
||||
<text class="header-title">我的订单</text>
|
||||
<view class="header-actions">
|
||||
<text class="search-icon" @click="navigateToSearch">🔍</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>
|
||||
</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"
|
||||
>
|
||||
<!-- 订单头部 -->
|
||||
<view class="order-header">
|
||||
<text class="order-no">订单号:{{ order.order_no }}</text>
|
||||
<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">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<text class="product-spec">{{ product.spec }}</text>
|
||||
<view class="product-footer">
|
||||
<text class="product-price">¥{{ product.price }}</text>
|
||||
<text class="product-quantity">×{{ product.quantity }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单信息 -->
|
||||
<view class="order-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">商品合计</text>
|
||||
<text class="info-value">¥{{ order.product_amount }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">运费</text>
|
||||
<text class="info-value">¥{{ order.shipping_fee }}</text>
|
||||
</view>
|
||||
<view class="info-row total">
|
||||
<text class="info-label">实付款</text>
|
||||
<text class="info-value total-price">¥{{ order.total_amount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单操作 -->
|
||||
<view class="order-actions">
|
||||
<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>
|
||||
</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>
|
||||
</view>
|
||||
|
||||
<view v-if="order.status === 4" class="action-buttons">
|
||||
<button class="action-btn review" @click="goReview(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 } from '@dcloudio/uni-app'
|
||||
// // import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
// 响应式数据
|
||||
const orders = ref<any[]>([])
|
||||
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 orderTabs = reactive([
|
||||
{ id: 'all', name: '全部', count: 12 },
|
||||
{ id: 'pending', name: '待付款', count: 2 },
|
||||
{ id: 'shipping', name: '待发货', count: 1 },
|
||||
{ id: 'delivering', name: '待收货', count: 3 },
|
||||
{ id: 'completed', name: '已完成', count: 5 },
|
||||
{ id: 'cancelled', name: '已取消', count: 1 }
|
||||
])
|
||||
|
||||
// Mock 订单数据
|
||||
const mockOrders = [
|
||||
{
|
||||
id: '202311230001',
|
||||
order_no: '202311230001',
|
||||
status: 1, // 1:待付款 2:待发货 3:待收货 4:已完成 5:已取消
|
||||
create_time: '2023-11-23 14:30:22',
|
||||
product_amount: 378.00,
|
||||
shipping_fee: 0.00,
|
||||
total_amount: 378.00,
|
||||
products: [
|
||||
{
|
||||
id: '1001',
|
||||
name: '无线蓝牙耳机 降噪版',
|
||||
price: 299.00,
|
||||
image: 'https://picsum.photos/80/80?random=1',
|
||||
spec: '白色',
|
||||
quantity: 1
|
||||
},
|
||||
{
|
||||
id: '1002',
|
||||
name: '耳机保护套',
|
||||
price: 29.00,
|
||||
image: 'https://picsum.photos/80/80?random=2',
|
||||
spec: '黑色',
|
||||
quantity: 1
|
||||
},
|
||||
{
|
||||
id: '1003',
|
||||
name: '数据线',
|
||||
price: 19.00,
|
||||
image: 'https://picsum.photos/80/80?random=3',
|
||||
spec: '1米',
|
||||
quantity: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '202311220001',
|
||||
order_no: '202311220001',
|
||||
status: 2,
|
||||
create_time: '2023-11-22 10:15:33',
|
||||
product_amount: 199.00,
|
||||
shipping_fee: 10.00,
|
||||
total_amount: 209.00,
|
||||
products: [
|
||||
{
|
||||
id: '2001',
|
||||
name: '运动T恤 速干面料',
|
||||
price: 79.00,
|
||||
image: 'https://picsum.photos/80/80?random=4',
|
||||
spec: '黑色 L',
|
||||
quantity: 2
|
||||
},
|
||||
{
|
||||
id: '2002',
|
||||
name: '运动短裤',
|
||||
price: 59.00,
|
||||
image: 'https://picsum.photos/80/80?random=5',
|
||||
spec: '黑色 M',
|
||||
quantity: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '202311210001',
|
||||
order_no: '202311210001',
|
||||
status: 3,
|
||||
create_time: '2023-11-21 16:45:12',
|
||||
product_amount: 299.00,
|
||||
shipping_fee: 0.00,
|
||||
total_amount: 299.00,
|
||||
products: [
|
||||
{
|
||||
id: '3001',
|
||||
name: '智能手环 心率监测',
|
||||
price: 199.00,
|
||||
image: 'https://picsum.photos/80/80?random=6',
|
||||
spec: '黑色',
|
||||
quantity: 1
|
||||
},
|
||||
{
|
||||
id: '3002',
|
||||
name: '手环腕带',
|
||||
price: 29.00,
|
||||
image: 'https://picsum.photos/80/80?random=7',
|
||||
spec: '蓝色',
|
||||
quantity: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '202311200001',
|
||||
order_no: '202311200001',
|
||||
status: 4,
|
||||
create_time: '2023-11-20 09:30:45',
|
||||
product_amount: 99.00,
|
||||
shipping_fee: 0.00,
|
||||
total_amount: 99.00,
|
||||
products: [
|
||||
{
|
||||
id: '4001',
|
||||
name: '保温杯 500ml',
|
||||
price: 49.00,
|
||||
image: 'https://picsum.photos/80/80?random=8',
|
||||
spec: '白色',
|
||||
quantity: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '202311190001',
|
||||
order_no: '202311190001',
|
||||
status: 5,
|
||||
create_time: '2023-11-19 14:20:18',
|
||||
product_amount: 599.00,
|
||||
shipping_fee: 0.00,
|
||||
total_amount: 599.00,
|
||||
products: [
|
||||
{
|
||||
id: '5001',
|
||||
name: '蓝牙音箱 便携式',
|
||||
price: 199.00,
|
||||
image: 'https://picsum.photos/80/80?random=9',
|
||||
spec: '黑色',
|
||||
quantity: 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// 计算属性:根据当前标签筛选订单
|
||||
const filteredOrders = computed(() => {
|
||||
if (activeTab.value === 'all') {
|
||||
return orders.value
|
||||
}
|
||||
|
||||
const statusMap: Record<string, number> = {
|
||||
'pending': 1,
|
||||
'shipping': 2,
|
||||
'delivering': 3,
|
||||
'completed': 4,
|
||||
'cancelled': 5
|
||||
}
|
||||
|
||||
const targetStatus = statusMap[activeTab.value]
|
||||
return orders.value.filter(order => order.status === targetStatus)
|
||||
})
|
||||
|
||||
// 生命周期
|
||||
onLoad((options) => {
|
||||
if (options['status']) {
|
||||
const status = options['status'] as string
|
||||
if (['all', 'pending', 'shipping', 'delivering', 'completed', 'cancelled'].includes(status)) {
|
||||
activeTab.value = status
|
||||
}
|
||||
}
|
||||
if (options['type']) {
|
||||
const type = options['type'] as string
|
||||
if (type === 'pending') activeTab.value = 'pending'
|
||||
else if (type === 'shipped') activeTab.value = 'delivering' // 映射到待收货
|
||||
else if (type === 'review') activeTab.value = 'completed' // 映射到已完成
|
||||
}
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
loadOrders()
|
||||
})
|
||||
|
||||
// 加载订单数据
|
||||
const loadOrders = async () => {
|
||||
loading.value = true
|
||||
const userStore = uni.getStorageSync('userInfo')
|
||||
const userId = userStore?.id
|
||||
|
||||
if (!userId) {
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 从本地存储获取订单
|
||||
const ordersStr = uni.getStorageSync('orders')
|
||||
let localOrders: any[] = []
|
||||
if (ordersStr) {
|
||||
localOrders = JSON.parse(ordersStr as string) as any[]
|
||||
}
|
||||
|
||||
// 过滤当前用户的订单
|
||||
// const userOrders = localOrders.filter((o: any) => o.user_id === userId)
|
||||
// 暂时显示所有订单用于测试
|
||||
let userOrders = localOrders
|
||||
|
||||
// 根据标签页过滤
|
||||
let filtered = userOrders
|
||||
const statusMap: Record<string, number> = {
|
||||
'pending': 1,
|
||||
'shipping': 2,
|
||||
'delivering': 3,
|
||||
'completed': 4,
|
||||
'cancelled': 5
|
||||
}
|
||||
|
||||
if (activeTab.value !== 'all') {
|
||||
const targetStatus = statusMap[activeTab.value]
|
||||
filtered = userOrders.filter((o: any) => o.status === targetStatus)
|
||||
}
|
||||
|
||||
// 按时间倒序
|
||||
filtered.sort((a: any, b: any) => {
|
||||
const timeA = new Date(a.created_at || a.create_time).getTime()
|
||||
const timeB = new Date(b.created_at || b.create_time).getTime()
|
||||
return timeB - timeA
|
||||
})
|
||||
|
||||
// 处理数据格式以适配当前页面
|
||||
orders.value = filtered.map((order: any) => ({
|
||||
id: order.id,
|
||||
order_no: order.order_no,
|
||||
status: order.status,
|
||||
create_time: order.created_at || order.create_time,
|
||||
product_amount: order.total_amount,
|
||||
shipping_fee: order.delivery_fee,
|
||||
total_amount: order.actual_amount,
|
||||
products: (order.items || order.products || []).map((item: any) => ({
|
||||
id: item.product_id || item.id,
|
||||
name: item.product_name || item.name,
|
||||
price: item.price,
|
||||
image: item.product_image || item.image || '/static/default-product.png',
|
||||
spec: item.sku_specifications ? formatSpec(item.sku_specifications) : (item.spec || ''),
|
||||
quantity: item.quantity
|
||||
}))
|
||||
}))
|
||||
|
||||
// 更新统计数据
|
||||
orderTabs[0].count = userOrders.length
|
||||
orderTabs[1].count = userOrders.filter((o: any) => o.status === 1).length
|
||||
orderTabs[2].count = userOrders.filter((o: any) => o.status === 2).length
|
||||
orderTabs[3].count = userOrders.filter((o: any) => o.status === 3).length
|
||||
orderTabs[4].count = userOrders.filter((o: any) => o.status === 4).length
|
||||
orderTabs[5].count = userOrders.filter((o: any) => o.status === 5).length
|
||||
|
||||
} catch (err) {
|
||||
console.error('加载订单异常:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
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')}`
|
||||
}
|
||||
|
||||
const formatSpec = (specs: any): string => {
|
||||
if (!specs) return ''
|
||||
if (typeof specs === 'object') {
|
||||
return Object.keys(specs).map(key => `${key}:${specs[key]}`).join(' ')
|
||||
}
|
||||
return String(specs)
|
||||
}
|
||||
|
||||
// 切换标签
|
||||
const switchTab = (tabId: string) => {
|
||||
activeTab.value = tabId
|
||||
page.value = 1
|
||||
orders.value = []
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: number): string => {
|
||||
const statusMap: Record<number, string> = {
|
||||
1: '待付款',
|
||||
2: '待发货',
|
||||
3: '待收货',
|
||||
4: '已完成',
|
||||
5: '已取消'
|
||||
}
|
||||
return statusMap[status] || '未知状态'
|
||||
}
|
||||
|
||||
// 获取状态类名
|
||||
const getStatusClass = (status: number): string => {
|
||||
const classMap: Record<number, string> = {
|
||||
1: 'status-pending',
|
||||
2: 'status-shipping',
|
||||
3: 'status-delivering',
|
||||
4: 'status-completed',
|
||||
5: 'status-cancelled'
|
||||
}
|
||||
return classMap[status] || '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(order => order.id === orderId)
|
||||
if (index !== -1) {
|
||||
orders.value[index].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}`
|
||||
})
|
||||
}
|
||||
|
||||
const confirmReceipt = (orderId: string) => {
|
||||
uni.showModal({
|
||||
title: '确认收货',
|
||||
content: '请确认您已收到商品,且商品无误',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 这里应该是实际的API调用
|
||||
uni.showToast({
|
||||
title: '收货成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 更新订单状态
|
||||
const index = orders.value.findIndex(order => order.id === orderId)
|
||||
if (index !== -1) {
|
||||
orders.value[index].status = 4
|
||||
orders.value = [...orders.value]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goReview = (order: any) => {
|
||||
const productIds = order.products.map((p: any) => p.id).join(',')
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/review?orderId=${order.id}&productIds=${productIds}`
|
||||
})
|
||||
}
|
||||
|
||||
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 navigateToSearch = () => {
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/search' })
|
||||
}
|
||||
|
||||
const navigateToProduct = (product: any) => {
|
||||
uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${product.id}` })
|
||||
}
|
||||
|
||||
const goShopping = () => {
|
||||
uni.switchTab({ url: '/pages/mall/consumer/index' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.orders-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.orders-header {
|
||||
background-color: white;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #eee;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-actions .search-icon {
|
||||
font-size: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 标签页 */
|
||||
.order-tabs {
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tab-scroll {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 10px;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.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: '';
|
||||
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 {
|
||||
height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-orders {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.order-product:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.product-spec {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.product-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 16px;
|
||||
color: #ff5000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.product-quantity {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 订单信息 */
|
||||
.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;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 6px 15px;
|
||||
border-radius: 15px;
|
||||
font-size: 13px;
|
||||
border: 1px solid;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.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: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.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 (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>
|
||||
@@ -2,11 +2,14 @@
|
||||
<template>
|
||||
<view class="orders-page">
|
||||
<!-- 顶部栏 -->
|
||||
<view class="orders-header">
|
||||
<view class="header-title">
|
||||
<text class="title-text">我的订单</text>
|
||||
<!-- <view class="orders-header" @click="goBackToHome">
|
||||
<view class="header-left">
|
||||
<text class="header-title">我的订单</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<text class="search-icon" @click.stop="navigateToSearch">🔍</text>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 订单状态标签页 -->
|
||||
<view class="order-tabs">
|
||||
@@ -153,8 +156,9 @@
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { onShow, onBackPress } from '@dcloudio/uni-app'
|
||||
import type { OrderType } from '@/types/mall-types.uts'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
// import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
type OrderItemType = {
|
||||
id: string
|
||||
@@ -203,53 +207,36 @@ onMounted(() => {
|
||||
|
||||
// 加载订单数量统计
|
||||
const loadOrderCounts = async () => {
|
||||
const userId = getCurrentUserId()
|
||||
const userId = getCurrentUserId() || 'user_001'
|
||||
if (!userId) return
|
||||
|
||||
try {
|
||||
// 待支付
|
||||
const { count: pendingCount, error: pendingError } = await supa
|
||||
.from('orders')
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('user_id', userId)
|
||||
.eq('status', 1)
|
||||
// 从本地存储获取订单
|
||||
const ordersStr = uni.getStorageSync('orders')
|
||||
let localOrders: any[] = []
|
||||
if (ordersStr) {
|
||||
localOrders = JSON.parse(ordersStr as string) as any[]
|
||||
}
|
||||
|
||||
// 过滤当前用户的订单
|
||||
const userOrders = localOrders.filter((o: any) => o.user_id === userId)
|
||||
|
||||
if (!pendingError) {
|
||||
tabBadges.value.pending = pendingCount || 0
|
||||
}
|
||||
// 待支付
|
||||
const pendingCount = userOrders.filter((o: any) => o.status === 1).length
|
||||
tabBadges.value.pending = pendingCount
|
||||
|
||||
// 待发货
|
||||
const { count: shippingCount, error: shippingError } = await supa
|
||||
.from('orders')
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('user_id', userId)
|
||||
.eq('status', 2)
|
||||
|
||||
if (!shippingError) {
|
||||
tabBadges.value.shipping = shippingCount || 0
|
||||
}
|
||||
const shippingCount = userOrders.filter((o: any) => o.status === 2).length
|
||||
tabBadges.value.shipping = shippingCount
|
||||
|
||||
// 待收货
|
||||
const { count: receivingCount, error: receivingError } = await supa
|
||||
.from('orders')
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('user_id', userId)
|
||||
.eq('status', 3)
|
||||
|
||||
if (!receivingError) {
|
||||
tabBadges.value.receiving = receivingCount || 0
|
||||
}
|
||||
const receivingCount = userOrders.filter((o: any) => o.status === 3).length
|
||||
tabBadges.value.receiving = receivingCount
|
||||
|
||||
// 待评价
|
||||
const { count: reviewCount, error: reviewError } = await supa
|
||||
.from('orders')
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('user_id', userId)
|
||||
.eq('status', 4)
|
||||
const reviewCount = userOrders.filter((o: any) => o.status === 4).length
|
||||
tabBadges.value.review = reviewCount
|
||||
|
||||
if (!reviewError) {
|
||||
tabBadges.value.review = reviewCount || 0
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载订单统计异常:', err)
|
||||
}
|
||||
@@ -257,16 +244,17 @@ const loadOrderCounts = async () => {
|
||||
|
||||
// 加载订单列表
|
||||
const loadOrders = async (loadMore: boolean = false) => {
|
||||
const userId = getCurrentUserId()
|
||||
const userId = getCurrentUserId() || 'user_001' // 默认用户ID
|
||||
if (!userId) {
|
||||
uni.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
})
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/login'
|
||||
})
|
||||
return
|
||||
// uni.showToast({
|
||||
// title: '请先登录',
|
||||
// icon: 'none'
|
||||
// })
|
||||
// uni.navigateTo({
|
||||
// url: '/pages/user/login'
|
||||
// })
|
||||
// 模拟已登录,用于测试
|
||||
// return
|
||||
}
|
||||
|
||||
if (isLoading.value || (!hasMore.value && loadMore)) {
|
||||
@@ -276,55 +264,47 @@ const loadOrders = async (loadMore: boolean = false) => {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
const page = loadMore ? currentPage.value + 1 : 1
|
||||
|
||||
// 构建查询条件
|
||||
let query = supa
|
||||
.from('orders')
|
||||
.select(`
|
||||
*,
|
||||
order_items (
|
||||
id,
|
||||
product_id,
|
||||
product_name,
|
||||
sku_specifications,
|
||||
price,
|
||||
quantity
|
||||
)
|
||||
`)
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false })
|
||||
const page = loadMore ? currentPage.value + 1 : 1
|
||||
// 从本地存储获取订单
|
||||
const ordersStr = uni.getStorageSync('orders')
|
||||
let localOrders: any[] = []
|
||||
if (ordersStr) {
|
||||
localOrders = JSON.parse(ordersStr as string) as any[]
|
||||
}
|
||||
|
||||
// 过滤当前用户的订单
|
||||
const userOrders = localOrders.filter((o: any) => o.user_id === userId)
|
||||
|
||||
// 根据标签页过滤
|
||||
let filteredOrders = userOrders
|
||||
switch (activeTab.value) {
|
||||
case 'pending':
|
||||
query = query.eq('status', 1)
|
||||
filteredOrders = userOrders.filter((o: any) => o.status === 1)
|
||||
break
|
||||
case 'shipping':
|
||||
query = query.eq('status', 2)
|
||||
filteredOrders = userOrders.filter((o: any) => o.status === 2)
|
||||
break
|
||||
case 'receiving':
|
||||
query = query.eq('status', 3)
|
||||
filteredOrders = userOrders.filter((o: any) => o.status === 3)
|
||||
break
|
||||
case 'review':
|
||||
query = query.eq('status', 4)
|
||||
filteredOrders = userOrders.filter((o: any) => o.status === 4)
|
||||
break
|
||||
}
|
||||
|
||||
// 按时间倒序
|
||||
filteredOrders.sort((a: any, b: any) => {
|
||||
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
})
|
||||
|
||||
// 分页
|
||||
query = query.range((page - 1) * pageSize.value, page * pageSize.value - 1)
|
||||
|
||||
const { data, error } = await query
|
||||
|
||||
if (error !== null) {
|
||||
console.error('加载订单失败:', error)
|
||||
return
|
||||
}
|
||||
|
||||
const newOrders = data ?? []
|
||||
// 分页 (模拟)
|
||||
const total = filteredOrders.length
|
||||
const start = (page - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
const pagedOrders = filteredOrders.slice(start, end)
|
||||
|
||||
// 处理订单数据,补充商品图片等信息
|
||||
const processedOrders = await processOrderData(newOrders)
|
||||
const processedOrders = await processOrderData(pagedOrders)
|
||||
|
||||
if (loadMore) {
|
||||
for (let i = 0; i < processedOrders.length; i++) {
|
||||
@@ -336,7 +316,7 @@ const loadOrders = async (loadMore: boolean = false) => {
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
hasMore.value = newOrders.length === pageSize.value
|
||||
hasMore.value = end < total
|
||||
} catch (err) {
|
||||
console.error('加载订单异常:', err)
|
||||
} finally {
|
||||
@@ -350,18 +330,26 @@ const processOrderData = async (orders: any[]): Promise<FullOrderType[]> => {
|
||||
|
||||
for (let i = 0; i < orders.length; i++) {
|
||||
const order = orders[i]
|
||||
const orderItems = order.order_items || []
|
||||
const orderItems = order.items || [] // 这里的 items 已经在 checkout 存入时有了
|
||||
|
||||
// 为每个商品项加载图片
|
||||
const itemsWithImages = await Promise.all(
|
||||
orderItems.map(async (item: any) => {
|
||||
const productImage = await getProductImage(item.product_id)
|
||||
return {
|
||||
...item,
|
||||
product_image: productImage
|
||||
}
|
||||
})
|
||||
)
|
||||
// const itemsWithImages = await Promise.all(
|
||||
// orderItems.map(async (item: any) => {
|
||||
// const productImage = await getProductImage(item.product_id)
|
||||
// return {
|
||||
// ...item,
|
||||
// product_image: productImage
|
||||
// }
|
||||
// })
|
||||
// )
|
||||
// 本地存储的 items 应该已经有 image 字段了,如果没有,再尝试获取
|
||||
const itemsWithImages = orderItems.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
product_image: item.product_image || item.image || '/static/default-product.png',
|
||||
product_name: item.product_name || item.name // 兼容不同字段名
|
||||
}
|
||||
})
|
||||
|
||||
processed.push({
|
||||
...order,
|
||||
@@ -450,6 +438,26 @@ const getTotalQuantity = (items: OrderItemType[]): number => {
|
||||
return items.reduce((total, item) => total + item.quantity, 0)
|
||||
}
|
||||
|
||||
// 返回主页
|
||||
const goBackToHome = () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/index'
|
||||
})
|
||||
}
|
||||
|
||||
// 导航到搜索页面
|
||||
const navigateToSearch = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/search'
|
||||
})
|
||||
}
|
||||
|
||||
// 监听手机返回键
|
||||
onBackPress(() => {
|
||||
goBackToHome()
|
||||
return true // 阻止默认返回行为
|
||||
})
|
||||
|
||||
// 标签页切换
|
||||
const changeTab = (tab: string) => {
|
||||
activeTab.value = tab
|
||||
@@ -684,32 +692,49 @@ const goToProfile = () => {
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 20px;
|
||||
color: #333333;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.order-tabs {
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.order-tab {
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
padding: 15px 10px;
|
||||
padding: 15px 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.order-tab.active {
|
||||
@@ -982,30 +1007,44 @@ const goToProfile = () => {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 底部导航栏 */
|
||||
.bottom-navigation {
|
||||
background-color: #ffffff;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 50px;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: #007aff;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 24px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-size: 10px;
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
.nav-item.active .nav-text {
|
||||
color: #4CAF50;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
1046
pages/mall/consumer/ordersx.uvue
Normal file
1046
pages/mall/consumer/ordersx.uvue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,26 @@ onMounted(() => {
|
||||
if (options.orderId) {
|
||||
orderId.value = options.orderId
|
||||
orderNo.value = options.orderId // 使用订单ID作为订单号
|
||||
amount.value = parseFloat(options.amount || 0)
|
||||
|
||||
// 优先使用传递的 amount
|
||||
if (options.amount) {
|
||||
amount.value = parseFloat(options.amount)
|
||||
} else {
|
||||
// 如果没有传 amount,尝试从本地存储查找订单
|
||||
try {
|
||||
const ordersStr = uni.getStorageSync('orders')
|
||||
if (ordersStr) {
|
||||
const orders = JSON.parse(ordersStr as string) as any[]
|
||||
const order = orders.find((o: any) => o.id === orderId.value)
|
||||
if (order) {
|
||||
amount.value = order.actual_amount || order.total_amount || 0
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('读取本地订单失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// loadOrderInfo() // 暂时注释掉数据库查询
|
||||
}
|
||||
})
|
||||
@@ -59,10 +78,8 @@ onMounted(() => {
|
||||
// }
|
||||
|
||||
const viewOrder = () => {
|
||||
// 跳转到订单列表或订单详情
|
||||
// 这里跳转到订单列表页,并选中对应tab(如果有)
|
||||
uni.redirectTo({
|
||||
url: '/pages/mall/consumer/orders?status=shipping'
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/orders'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
<!-- 支付页面 -->
|
||||
<template>
|
||||
<view class="payment-page">
|
||||
<!-- 顶部栏 -->
|
||||
<view class="payment-header">
|
||||
<text class="back-btn" @click="goBack">‹</text>
|
||||
<text class="header-title">收银台</text>
|
||||
</view>
|
||||
|
||||
<view class="payment-content">
|
||||
<!-- 支付金额 -->
|
||||
<view class="amount-section">
|
||||
<text class="amount-label">支付金额</text>
|
||||
<text class="amount-value">¥{{ amount.toFixed(2) }}</text>
|
||||
<!-- 价格明细 -->
|
||||
<view class="price-detail-section">
|
||||
<text class="section-title">价格明细</text>
|
||||
<view class="price-detail">
|
||||
<view class="price-row">
|
||||
<text class="price-label">商品总价</text>
|
||||
<text class="price-value">¥{{ productAmount.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view class="price-row">
|
||||
<text class="price-label">运费</text>
|
||||
<text class="price-value">+¥{{ deliveryFee.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view v-if="discountAmount > 0" class="price-row">
|
||||
<text class="price-label">优惠减免</text>
|
||||
<text class="price-value discount">-¥{{ discountAmount.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view class="price-row total">
|
||||
<text class="price-label">应付金额</text>
|
||||
<text class="price-value total-price">¥{{ amount.toFixed(2) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="order-no">订单号: {{ orderNo }}</text>
|
||||
</view>
|
||||
|
||||
@@ -98,7 +109,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ref, onMounted, watch, computed, onUnmounted } from 'vue'
|
||||
import { onLoad, onBackPress } from '@dcloudio/uni-app'
|
||||
// import { supabase as supa } from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
type PaymentMethodType = {
|
||||
@@ -119,6 +131,11 @@ const isPaying = ref<boolean>(false)
|
||||
const showPassword = ref<boolean>(false)
|
||||
const password = ref<string>('')
|
||||
|
||||
// 价格相关变量
|
||||
const productAmount = ref<number>(0) // 商品总价
|
||||
const deliveryFee = ref<number>(0) // 运费
|
||||
const discountAmount = ref<number>(0) // 优惠减免
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
const pages = getCurrentPages()
|
||||
@@ -133,11 +150,65 @@ onMounted(() => {
|
||||
if (options.amount) {
|
||||
amount.value = parseFloat(options.amount)
|
||||
}
|
||||
|
||||
// 获取传递的价格详情
|
||||
if (options.productAmount) {
|
||||
productAmount.value = parseFloat(options.productAmount)
|
||||
}
|
||||
if (options.deliveryFee) {
|
||||
deliveryFee.value = parseFloat(options.deliveryFee)
|
||||
}
|
||||
if (options.discountAmount) {
|
||||
discountAmount.value = parseFloat(options.discountAmount)
|
||||
}
|
||||
|
||||
// 如果没有传详情,尝试根据总价估算(兼容旧逻辑,但优先使用传参)
|
||||
if (!options.productAmount && amount.value > 0) {
|
||||
calculatePriceDetails(amount.value)
|
||||
}
|
||||
|
||||
loadPaymentMethods()
|
||||
loadUserBalance()
|
||||
})
|
||||
|
||||
// 监听返回操作(包含系统返回键和导航栏返回按钮)
|
||||
onBackPress((options) => {
|
||||
// 如果是通过代码主动调用 navigateBack 返回,则允许
|
||||
if (options.from === 'navigateBack') {
|
||||
return false
|
||||
}
|
||||
|
||||
// 否则拦截返回,显示确认弹窗
|
||||
goBack()
|
||||
return true
|
||||
})
|
||||
|
||||
// 更新本地存储中的订单状态
|
||||
const updateOrderInStorage = (status: number) => {
|
||||
try {
|
||||
// 尝试从 'orders' 读取 (checkout页面写入的key)
|
||||
const ordersStr = uni.getStorageSync('orders')
|
||||
let orders: any[] = []
|
||||
if (ordersStr) {
|
||||
orders = JSON.parse(ordersStr as string) as any[]
|
||||
}
|
||||
|
||||
const index = orders.findIndex((o: any) => o.id === orderId.value)
|
||||
if (index !== -1) {
|
||||
orders[index].status = status
|
||||
orders[index].payment_status = status === 2 ? 1 : 0 // 2=待发货(已支付), 1=待支付(未支付)
|
||||
orders[index].updated_at = new Date().toISOString()
|
||||
// 确保更新的是 'orders' key
|
||||
uni.setStorageSync('orders', JSON.stringify(orders))
|
||||
console.log('订单状态已更新到Storage (orders):', orderId.value, status)
|
||||
} else {
|
||||
console.warn('在Storage (orders)中未找到订单:', orderId.value)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('更新订单状态失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载订单信息
|
||||
const loadOrderInfo = async () => {
|
||||
try {
|
||||
@@ -274,6 +345,46 @@ const getPayButtonText = (): string => {
|
||||
return texts[selectedMethod.value] || '确认支付'
|
||||
}
|
||||
|
||||
// 减少商品库存
|
||||
const reduceStock = (orderId: string) => {
|
||||
try {
|
||||
// 读取订单
|
||||
const ordersStr = uni.getStorageSync('orders')
|
||||
if (!ordersStr) return
|
||||
|
||||
const orders = JSON.parse(ordersStr as string) as any[]
|
||||
const order = orders.find((o: any) => o.id === orderId)
|
||||
|
||||
if (!order || !order.items) return
|
||||
|
||||
// 读取商品库(这里假设商品库也在本地,实际项目中通常在服务器端处理)
|
||||
// 模拟:如果有本地商品缓存,则更新
|
||||
/*
|
||||
const productsStr = uni.getStorageSync('products')
|
||||
if (productsStr) {
|
||||
const products = JSON.parse(productsStr as string) as any[]
|
||||
let hasChange = false
|
||||
|
||||
order.items.forEach((item: any) => {
|
||||
const product = products.find((p: any) => p.id === item.product_id)
|
||||
if (product && product.stock >= item.quantity) {
|
||||
product.stock -= item.quantity
|
||||
hasChange = true
|
||||
console.log(`商品 ${product.name} 库存减少 ${item.quantity}, 剩余 ${product.stock}`)
|
||||
}
|
||||
})
|
||||
|
||||
if (hasChange) {
|
||||
uni.setStorageSync('products', JSON.stringify(products))
|
||||
}
|
||||
}
|
||||
*/
|
||||
console.log('模拟扣减库存成功', order.items)
|
||||
} catch (e) {
|
||||
console.error('扣减库存失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 确认支付
|
||||
const confirmPayment = async () => {
|
||||
if (isPaying.value) return
|
||||
@@ -309,6 +420,11 @@ const confirmPayment = async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
|
||||
// 更新订单状态
|
||||
updateOrderInStorage(2) // 2: 待发货(已支付)
|
||||
|
||||
// 扣减库存
|
||||
reduceStock(orderId.value)
|
||||
|
||||
/* const { error } = await supa
|
||||
.from('orders')
|
||||
.update({
|
||||
@@ -335,6 +451,9 @@ const confirmPayment = async () => {
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
// 发布订单更新事件,让profile页面可以刷新数据
|
||||
uni.$emit('orderUpdated', { orderId: orderId.value, status: 2 }) // 2: 待发货
|
||||
|
||||
// 跳转到支付成功页面
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({
|
||||
@@ -471,9 +590,91 @@ const forgotPassword = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 计算价格明细
|
||||
const calculatePriceDetails = (totalAmount: number) => {
|
||||
// 模拟计算各项费用
|
||||
// 假设商品总价占总金额的80%,运费占10%,优惠减免占10%
|
||||
productAmount.value = totalAmount * 0.8
|
||||
deliveryFee.value = totalAmount * 0.1
|
||||
discountAmount.value = totalAmount * 0.1
|
||||
|
||||
// 确保总和等于应付金额
|
||||
const calculatedTotal = productAmount.value + deliveryFee.value - discountAmount.value
|
||||
if (Math.abs(calculatedTotal - totalAmount) > 0.01) {
|
||||
// 调整商品总价以匹配应付金额
|
||||
productAmount.value = totalAmount + discountAmount.value - deliveryFee.value
|
||||
}
|
||||
}
|
||||
|
||||
// 在组件卸载时移除返回键监听
|
||||
onUnmounted(() => {
|
||||
// uni.offBackPress() 在uni-app中不需要手动移除
|
||||
})
|
||||
|
||||
// 返回
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
uni.showModal({
|
||||
title: '取消支付',
|
||||
content: '确定要取消支付吗?取消后订单将保存到待支付订单中',
|
||||
confirmText: '取消支付',
|
||||
cancelText: '继续支付',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
// 用户确认取消支付,更新订单状态为待支付
|
||||
await cancelPayment()
|
||||
} else {
|
||||
// 用户选择继续支付,留在当前页面
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 取消支付,更新订单状态
|
||||
const cancelPayment = async () => {
|
||||
try {
|
||||
// 这里应该调用API更新订单状态为待支付(status: 1)
|
||||
// 模拟更新订单状态
|
||||
/* const { error } = await supa
|
||||
.from('orders')
|
||||
.update({
|
||||
status: 1, // 待支付
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.eq('id', orderId.value)
|
||||
|
||||
if (error !== null) {
|
||||
console.error('更新订单状态失败:', error)
|
||||
uni.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
} */
|
||||
|
||||
// 更新本地存储
|
||||
updateOrderInStorage(orderId.value, 1) // 1: 待支付
|
||||
|
||||
// 发布订单更新事件,让profile页面可以刷新数据
|
||||
uni.$emit('orderUpdated', { orderId: orderId.value, status: 1 })
|
||||
|
||||
uni.showToast({
|
||||
title: '已保存到待支付订单',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 延迟返回,让用户看到提示
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
|
||||
} catch (err) {
|
||||
console.error('取消支付异常:', err)
|
||||
uni.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -511,32 +712,58 @@ const goBack = () => {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.amount-section {
|
||||
/* 价格明细部分 */
|
||||
.price-detail-section {
|
||||
background-color: #ffffff;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
padding: 20px 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.amount-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
.price-detail {
|
||||
padding: 15px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
display: block;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
.price-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.price-row.total {
|
||||
border-top: 1px solid #e5e5e5;
|
||||
margin-top: 8px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.price-value.discount {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.price-value.total-price {
|
||||
font-size: 18px;
|
||||
color: #ff4757;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.methods-section {
|
||||
@@ -758,4 +985,4 @@ const goBack = () => {
|
||||
font-size: 24px;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -41,6 +41,26 @@
|
||||
<text class="spec-arrow">></text>
|
||||
</view>
|
||||
|
||||
<!-- 数量选择 -->
|
||||
<view class="quantity-section">
|
||||
<text class="quantity-title">数量</text>
|
||||
<view class="quantity-selector">
|
||||
<view class="quantity-btn minus" @click="decreaseQuantity">
|
||||
<text class="quantity-btn-text">-</text>
|
||||
</view>
|
||||
<input class="quantity-input"
|
||||
type="number"
|
||||
v-model="quantity"
|
||||
:min="1"
|
||||
:max="getMaxQuantity()"
|
||||
@input="validateQuantity" />
|
||||
<view class="quantity-btn plus" @click="increaseQuantity">
|
||||
<text class="quantity-btn-text">+</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="quantity-stock">库存{{ getAvailableStock() }}件</text>
|
||||
</view>
|
||||
|
||||
<!-- 商品详情 -->
|
||||
<view class="product-description">
|
||||
<view class="section-title">商品详情</view>
|
||||
@@ -131,13 +151,32 @@ export default {
|
||||
}
|
||||
},
|
||||
onLoad(options: any) {
|
||||
const productId = options.productId as string
|
||||
const productId = options.productId as string || options.id as string
|
||||
const productPrice = options.price ? parseFloat(options.price) : null
|
||||
const productOriginalPrice = options.original_price ? parseFloat(options.original_price) : null
|
||||
const productName = options.name as string
|
||||
const productImage = options.image as string
|
||||
|
||||
if (productId) {
|
||||
this.loadProductDetail(productId)
|
||||
this.loadProductDetail(productId, {
|
||||
price: productPrice,
|
||||
originalPrice: productOriginalPrice,
|
||||
name: productName,
|
||||
image: productImage
|
||||
})
|
||||
this.checkFavoriteStatus(productId)
|
||||
this.saveFootprint(productId)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayPrice(): number {
|
||||
if (this.selectedSkuId) {
|
||||
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
|
||||
if (sku) return sku.price
|
||||
}
|
||||
return this.product.price
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveFootprint(productId: string) {
|
||||
const footprintData = uni.getStorageSync('footprints')
|
||||
@@ -175,39 +214,89 @@ export default {
|
||||
uni.setStorageSync('footprints', JSON.stringify(footprints))
|
||||
},
|
||||
|
||||
loadProductDetail(productId: string) {
|
||||
// 模拟加载商品详情数据
|
||||
loadProductDetail(productId: string, options: any = {}) {
|
||||
// 根据商品ID生成一个基础价格(如果没有传入价格)
|
||||
const generatePriceFromId = (id: string): number => {
|
||||
// 简单哈希函数,将字符串转换为一个在50-500之间的价格
|
||||
let hash = 0
|
||||
for (let i = 0; i < id.length; i++) {
|
||||
hash = (hash << 5) - hash + id.charCodeAt(i)
|
||||
hash |= 0 // 转换为32位整数
|
||||
}
|
||||
// 将哈希值映射到50-500之间
|
||||
const price = 50 + Math.abs(hash % 450)
|
||||
// 保留两位小数
|
||||
return parseFloat(price.toFixed(2))
|
||||
}
|
||||
|
||||
// 优先使用传入的参数,否则根据商品ID生成价格
|
||||
const basePrice = options.price ? parseFloat(options.price) : generatePriceFromId(productId)
|
||||
// 原价比现价高20%左右
|
||||
const originalPrice = options.originalPrice ? parseFloat(options.originalPrice) : parseFloat((basePrice * 1.2).toFixed(2))
|
||||
|
||||
// 根据商品ID生成不同的商品名称,使其更真实
|
||||
const productNames = [
|
||||
'高品质运动休闲鞋',
|
||||
'时尚简约双肩背包',
|
||||
'多功能智能手环',
|
||||
'便携式蓝牙音箱',
|
||||
'全自动雨伞',
|
||||
'抗菌防螨床上四件套',
|
||||
'不锈钢保温杯',
|
||||
'无线充电器',
|
||||
'高清行车记录仪',
|
||||
'智能体脂秤'
|
||||
]
|
||||
const nameIndex = Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % productNames.length
|
||||
const productName = options.name ? options.name : productNames[nameIndex]
|
||||
|
||||
// 模拟销量和库存,使其更真实
|
||||
const sales = 1000 + Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5000
|
||||
const stock = 50 + Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 200
|
||||
|
||||
this.product = {
|
||||
id: productId,
|
||||
merchant_id: 'merchant_001',
|
||||
category_id: 'cat_001',
|
||||
name: '精选好物商品',
|
||||
description: '这是一个高品质的商品,具有优秀的性能和优美的外观设计。',
|
||||
merchant_id: 'merchant_' + (Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5 + 1).toString().padStart(3, '0'),
|
||||
category_id: 'cat_' + (Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 10 + 1).toString().padStart(3, '0'),
|
||||
name: productName,
|
||||
description: '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
|
||||
images: [
|
||||
'/static/product1.jpg',
|
||||
'/static/product2.jpg',
|
||||
'/static/product3.jpg'
|
||||
],
|
||||
price: 199.99,
|
||||
original_price: 299.99,
|
||||
stock: 100,
|
||||
sales: 1256,
|
||||
price: basePrice,
|
||||
original_price: originalPrice,
|
||||
stock: stock,
|
||||
sales: sales,
|
||||
status: 1,
|
||||
created_at: '2024-01-15'
|
||||
}
|
||||
|
||||
// 根据商家ID生成不同的商家信息
|
||||
const merchantIndex = Math.abs(this.product.merchant_id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5
|
||||
const shopNames = ['优质好店', '品牌直营店', '官方旗舰店', '专卖店', '精品小店']
|
||||
const shopDescriptions = [
|
||||
'专注品质生活',
|
||||
'品牌官方直营,正品保障',
|
||||
'厂家直销,价格优惠',
|
||||
'专注本领域十年老店',
|
||||
'用心服务每一位顾客'
|
||||
]
|
||||
const contactNames = ['店主小王', '店长小李', '经理小张', '客服小赵', '老板小钱']
|
||||
|
||||
this.merchant = {
|
||||
id: 'merchant_001',
|
||||
user_id: 'user_001',
|
||||
shop_name: '优质好店',
|
||||
id: this.product.merchant_id,
|
||||
user_id: 'user_' + (merchantIndex + 1).toString().padStart(3, '0'),
|
||||
shop_name: shopNames[merchantIndex],
|
||||
shop_logo: '/static/shop-logo.png',
|
||||
shop_banner: '/static/shop-banner.png',
|
||||
shop_description: '专注品质生活',
|
||||
contact_name: '店主小王',
|
||||
contact_phone: '13800138000',
|
||||
shop_description: shopDescriptions[merchantIndex],
|
||||
contact_name: contactNames[merchantIndex],
|
||||
contact_phone: '138' + (10000000 + merchantIndex * 1111111).toString().substring(0, 8),
|
||||
shop_status: 1,
|
||||
rating: 4.8,
|
||||
total_sales: 15680,
|
||||
rating: 4.5 + (merchantIndex * 0.1),
|
||||
total_sales: 10000 + merchantIndex * 5000,
|
||||
created_at: '2023-06-01'
|
||||
}
|
||||
|
||||
@@ -216,13 +305,15 @@ export default {
|
||||
|
||||
loadProductSkus(productId: string) {
|
||||
// 模拟加载商品SKU数据
|
||||
const basePrice = this.product.price
|
||||
|
||||
this.productSkus = [
|
||||
{
|
||||
id: 'sku_001',
|
||||
product_id: productId,
|
||||
sku_code: 'SKU001',
|
||||
specifications: { color: '红色', size: 'M' },
|
||||
price: 199.99,
|
||||
price: basePrice,
|
||||
stock: 50,
|
||||
image_url: '/static/sku1.jpg',
|
||||
status: 1
|
||||
@@ -232,7 +323,7 @@ export default {
|
||||
product_id: productId,
|
||||
sku_code: 'SKU002',
|
||||
specifications: { color: '蓝色', size: 'L' },
|
||||
price: 219.99,
|
||||
price: parseFloat((basePrice * 1.1).toFixed(2)),
|
||||
stock: 30,
|
||||
image_url: '/static/sku2.jpg',
|
||||
status: 1
|
||||
@@ -328,16 +419,30 @@ export default {
|
||||
|
||||
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
|
||||
|
||||
const selectedItem = {
|
||||
id: this.selectedSkuId,
|
||||
product_id: this.product.id,
|
||||
sku_id: this.selectedSkuId,
|
||||
product_name: this.product.name,
|
||||
product_image: (sku && sku.image_url) ? sku.image_url : this.product.images[0],
|
||||
sku_specifications: sku ? sku.specifications : {},
|
||||
price: sku ? sku.price : this.product.price,
|
||||
quantity: this.quantity
|
||||
}
|
||||
// 调试:打印价格信息
|
||||
console.log('立即购买 - 商品价格信息:')
|
||||
console.log('SKU价格:', sku ? sku.price : '无SKU')
|
||||
console.log('商品价格:', this.product.price)
|
||||
console.log('选择的价格:', (sku ? sku.price : this.product.price))
|
||||
console.log('数量:', this.quantity)
|
||||
|
||||
const selectedItem = {
|
||||
id: this.selectedSkuId,
|
||||
product_id: this.product.id,
|
||||
sku_id: this.selectedSkuId,
|
||||
product_name: this.product.name,
|
||||
product_image: (sku && sku.image_url) ? sku.image_url : this.product.images[0],
|
||||
sku_specifications: sku ? sku.specifications : {},
|
||||
price: Number(parseFloat((sku ? sku.price : this.product.price).toString()).toFixed(2)),
|
||||
quantity: Number(this.quantity)
|
||||
}
|
||||
|
||||
// 调试:打印最终传递的数据
|
||||
console.log('立即购买 - 传递的商品数据:', selectedItem)
|
||||
|
||||
// 使用Storage传递数据,避免EventChannel可能的问题
|
||||
uni.setStorageSync('checkout_type', 'buy_now')
|
||||
uni.setStorageSync('checkout_items', JSON.stringify([selectedItem]))
|
||||
|
||||
// 跳转到订单确认页
|
||||
uni.navigateTo({
|
||||
@@ -393,6 +498,7 @@ export default {
|
||||
id: this.product.id,
|
||||
name: this.product.name,
|
||||
price: this.product.price,
|
||||
original_price: this.product.original_price, // 保存原价
|
||||
image: this.product.images[0],
|
||||
sales: this.product.sales,
|
||||
shopId: this.merchant.id,
|
||||
@@ -418,6 +524,60 @@ export default {
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/cart'
|
||||
})
|
||||
},
|
||||
|
||||
// 数量选择相关方法
|
||||
decreaseQuantity() {
|
||||
if (this.quantity > 1) {
|
||||
this.quantity--
|
||||
}
|
||||
},
|
||||
|
||||
increaseQuantity() {
|
||||
const maxQuantity = this.getMaxQuantity()
|
||||
if (this.quantity < maxQuantity) {
|
||||
this.quantity++
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: `最多只能购买${maxQuantity}件`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
validateQuantity() {
|
||||
// 确保数量是数字
|
||||
let num = parseInt(this.quantity)
|
||||
if (isNaN(num)) {
|
||||
num = 1
|
||||
}
|
||||
|
||||
// 限制在1和最大库存之间
|
||||
const maxQuantity = this.getMaxQuantity()
|
||||
if (num < 1) {
|
||||
num = 1
|
||||
} else if (num > maxQuantity) {
|
||||
num = maxQuantity
|
||||
uni.showToast({
|
||||
title: `最多只能购买${maxQuantity}件`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
this.quantity = num
|
||||
},
|
||||
|
||||
getMaxQuantity() {
|
||||
// 如果有选择SKU,使用SKU的库存,否则使用商品总库存
|
||||
if (this.selectedSkuId) {
|
||||
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
|
||||
if (sku) return sku.stock
|
||||
}
|
||||
return this.product.stock
|
||||
},
|
||||
|
||||
getAvailableStock() {
|
||||
return this.getMaxQuantity()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -565,6 +725,66 @@ export default {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.quantity-section {
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.quantity-title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
width: 120rpx;
|
||||
}
|
||||
|
||||
.quantity-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quantity-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.quantity-btn.minus {
|
||||
border-right: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.quantity-btn.plus {
|
||||
border-left: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.quantity-btn-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.quantity-input {
|
||||
width: 80rpx;
|
||||
height: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
border: none;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.quantity-stock {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.product-description {
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<view class="nav-stat-item">
|
||||
<text class="nav-stat-label">余额</text>
|
||||
<text class="nav-stat-value">¥{{ userStats.balance }}</text>
|
||||
<text class="nav-stat-value" @click="goToWallet">¥{{ userStats.balance }}</text>
|
||||
</view>
|
||||
|
||||
<view class="nav-stat-item" @click="goToCoupons">
|
||||
@@ -99,16 +99,16 @@
|
||||
<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 === 'shipped' }" @click="switchOrderTab('shipped')">
|
||||
<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 class="order-tab" :class="{ active: currentOrderTab === 'review' }" @click="switchOrderTab('review')">
|
||||
<text class="tab-icon">⭐</text>
|
||||
<text class="tab-text">待评价</text>
|
||||
<text v-if="orderCounts.review > 0" class="tab-badge">{{ orderCounts.review }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 账户安全 -->
|
||||
<view class="account-security">
|
||||
<!-- <view class="account-security">
|
||||
<view class="section-title">账户安全</view>
|
||||
<view class="security-items">
|
||||
<view class="security-item" @click="changePassword">
|
||||
@@ -242,7 +242,7 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -259,8 +259,8 @@ type UserStatsType = {
|
||||
type OrderCountsType = {
|
||||
total: number
|
||||
pending: number
|
||||
toship: number
|
||||
shipped: number
|
||||
review: number
|
||||
}
|
||||
|
||||
type ServiceCountsType = {
|
||||
@@ -302,9 +302,9 @@ export default {
|
||||
orderCounts: {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
toship: 0,
|
||||
shipped: 0,
|
||||
review: 0
|
||||
} as OrderCountsType,
|
||||
} as any,
|
||||
serviceCounts: {
|
||||
coupons: 0,
|
||||
favorites: 0
|
||||
@@ -332,10 +332,17 @@ export default {
|
||||
this.initPage()
|
||||
this.loadUserProfile()
|
||||
this.loadOrders()
|
||||
|
||||
// 监听订单更新事件
|
||||
uni.$on('orderUpdated', this.handleOrderUpdated)
|
||||
},
|
||||
onShow() {
|
||||
this.refreshData()
|
||||
},
|
||||
onUnload() {
|
||||
// 移除事件监听
|
||||
uni.$off('orderUpdated', this.handleOrderUpdated)
|
||||
},
|
||||
computed: {
|
||||
// 根据当前Tab筛选订单
|
||||
filteredOrders(): Array<OrderType> {
|
||||
@@ -343,8 +350,10 @@ export default {
|
||||
return this.allOrders
|
||||
} else if (this.currentOrderTab === 'pending') {
|
||||
return this.allOrders.filter((order: OrderType): boolean => order.status === 1)
|
||||
} else if (this.currentOrderTab === 'toship') {
|
||||
return this.allOrders.filter((order: OrderType): boolean => order.status === 2)
|
||||
} else if (this.currentOrderTab === 'shipped') {
|
||||
return this.allOrders.filter((order: OrderType): boolean => order.status === 2 || order.status === 3)
|
||||
return this.allOrders.filter((order: OrderType): boolean => order.status === 3)
|
||||
} else if (this.currentOrderTab === 'review') {
|
||||
return this.allOrders.filter((order: OrderType): boolean => order.status === 4)
|
||||
}
|
||||
@@ -355,40 +364,32 @@ export default {
|
||||
// 加载订单数据
|
||||
async loadOrders() {
|
||||
const userStore = uni.getStorageSync('userInfo')
|
||||
const userId = userStore?.id
|
||||
if (!userId) return
|
||||
// const userId = userStore?.id
|
||||
// if (!userId) return
|
||||
|
||||
try {
|
||||
/* const { data, error } = await supa
|
||||
.from('orders')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false })
|
||||
|
||||
if (error != null) {
|
||||
console.error('加载订单失败', error)
|
||||
return
|
||||
// 从本地存储加载订单数据
|
||||
const storedOrders = uni.getStorageSync('orders')
|
||||
let orders: any[] = []
|
||||
if (storedOrders) {
|
||||
orders = JSON.parse(storedOrders as string) as any[]
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
this.allOrders = data as any[]
|
||||
this.recentOrders = this.allOrders
|
||||
|
||||
// 更新角标统计
|
||||
this.orderCounts = {
|
||||
total: this.allOrders.length,
|
||||
pending: this.allOrders.filter((o: any) => o.status === 1).length,
|
||||
shipped: this.allOrders.filter((o: any) => o.status === 2 || o.status === 3).length,
|
||||
review: this.allOrders.filter((o: any) => o.status === 4).length
|
||||
}
|
||||
} */
|
||||
|
||||
// MOCK ORDERS
|
||||
this.allOrders = this.recentOrders
|
||||
this.allOrders = orders
|
||||
// 按时间倒序
|
||||
this.allOrders.sort((a: any, b: any) => {
|
||||
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
})
|
||||
|
||||
// 过滤最近的订单
|
||||
this.recentOrders = this.allOrders.slice(0, 5)
|
||||
|
||||
// 更新角标统计 (确保状态码一致: 1=待支付, 2=待发货, 3=待收货, 4=待评价)
|
||||
this.orderCounts = {
|
||||
total: this.allOrders.length,
|
||||
pending: this.allOrders.filter((o: any) => o.status === 1).length,
|
||||
shipped: this.allOrders.filter((o: any) => o.status === 2 || o.status === 3).length,
|
||||
toship: this.allOrders.filter((o: any) => o.status === 2).length, // 修复:仅计算状态2为待发货
|
||||
shipped: this.allOrders.filter((o: any) => o.status === 3).length, // 修复:仅计算状态3为待收货
|
||||
review: this.allOrders.filter((o: any) => o.status === 4).length
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -581,12 +582,20 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
// 跳转设置
|
||||
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}`
|
||||
@@ -696,6 +705,26 @@ export default {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/bind-email'
|
||||
})
|
||||
},
|
||||
|
||||
// 处理订单更新事件
|
||||
handleOrderUpdated(data: any) {
|
||||
// 当收到订单更新事件时,刷新订单数据
|
||||
console.log('收到订单更新事件:', data)
|
||||
this.refreshData()
|
||||
|
||||
// 显示提示
|
||||
if (data.status === 1) {
|
||||
uni.showToast({
|
||||
title: '订单已保存到待支付',
|
||||
icon: 'success'
|
||||
})
|
||||
} else if (data.status === 2) {
|
||||
uni.showToast({
|
||||
title: '支付成功,订单待发货',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,7 +537,7 @@ const viewProductDetail = (item: any) => {
|
||||
// 跳转详情页逻辑
|
||||
console.log('查看商品', item)
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/product-detail?productId=${item.id}`
|
||||
url: `/pages/mall/consumer/product-detail?productId=${item.id}&price=${item.price}&originalPrice=${item.originalPrice || ''}`
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
702
pages/mall/consumer/settings - 副本.uvue
Normal file
702
pages/mall/consumer/settings - 副本.uvue
Normal file
@@ -0,0 +1,702 @@
|
||||
<!-- 设置页面 -->
|
||||
<template>
|
||||
<view class="settings-page">
|
||||
<!-- 顶部栏 -->
|
||||
<view class="settings-header">
|
||||
<text class="back-btn" @click="goBack">‹</text>
|
||||
<text class="header-title">设置</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="settings-content" scroll-y>
|
||||
<!-- 账户设置 -->
|
||||
<view class="settings-section">
|
||||
<text class="section-title">账户设置</text>
|
||||
<view class="section-list">
|
||||
<view class="list-item" @click="goToProfile">
|
||||
<text class="item-icon">👤</text>
|
||||
<text class="item-text">个人资料</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="goToAddress">
|
||||
<text class="item-icon">📍</text>
|
||||
<text class="item-text">收货地址</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="changePassword">
|
||||
<text class="item-icon">🔒</text>
|
||||
<text class="item-text">修改密码</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="bindPhone">
|
||||
<text class="item-icon">📱</text>
|
||||
<text class="item-text">手机绑定</text>
|
||||
<view class="item-right">
|
||||
<text class="item-status" :class="{ bound: userInfo.phone }">
|
||||
{{ userInfo.phone ? '已绑定' : '未绑定' }}
|
||||
</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-item" @click="bindEmail">
|
||||
<text class="item-icon">📧</text>
|
||||
<text class="item-text">邮箱绑定</text>
|
||||
<view class="item-right">
|
||||
<text class="item-status" :class="{ bound: userInfo.email }">
|
||||
{{ userInfo.email ? '已绑定' : '未绑定' }}
|
||||
</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 消息通知 -->
|
||||
<view class="settings-section">
|
||||
<text class="section-title">消息通知</text>
|
||||
<view class="section-list">
|
||||
<view class="list-item">
|
||||
<text class="item-icon">🔔</text>
|
||||
<text class="item-text">订单消息</text>
|
||||
<switch :checked="notifications.order" @change="toggleNotification('order')" />
|
||||
</view>
|
||||
<view class="list-item">
|
||||
<text class="item-icon">🎁</text>
|
||||
<text class="item-text">促销活动</text>
|
||||
<switch :checked="notifications.promotion" @change="toggleNotification('promotion')" />
|
||||
</view>
|
||||
<view class="list-item">
|
||||
<text class="item-icon">⭐</text>
|
||||
<text class="item-text">评价提醒</text>
|
||||
<switch :checked="notifications.review" @change="toggleNotification('review')" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 隐私设置 -->
|
||||
<view class="settings-section">
|
||||
<text class="section-title">隐私设置</text>
|
||||
<view class="section-list">
|
||||
<view class="list-item">
|
||||
<text class="item-icon">👁️</text>
|
||||
<text class="item-text">隐藏购物记录</text>
|
||||
<switch :checked="privacy.hidePurchase" @change="togglePrivacy('hidePurchase')" />
|
||||
</view>
|
||||
<view class="list-item">
|
||||
<text class="item-icon">🔍</text>
|
||||
<text class="item-text">允许通过手机号找到我</text>
|
||||
<switch :checked="privacy.allowSearchByPhone" @change="togglePrivacy('allowSearchByPhone')" />
|
||||
</view>
|
||||
<view class="list-item">
|
||||
<text class="item-icon">💬</text>
|
||||
<text class="item-text">接收商家消息</text>
|
||||
<switch :checked="privacy.receiveMerchantMsg" @change="togglePrivacy('receiveMerchantMsg')" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 通用设置 -->
|
||||
<view class="settings-section">
|
||||
<text class="section-title">通用设置</text>
|
||||
<view class="section-list">
|
||||
<view class="list-item" @click="clearCache">
|
||||
<text class="item-icon">🗑️</text>
|
||||
<text class="item-text">清除缓存</text>
|
||||
<view class="item-right">
|
||||
<text class="item-cache">{{ cacheSize }}</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-item" @click="changeLanguage">
|
||||
<text class="item-icon">🌐</text>
|
||||
<text class="item-text">语言设置</text>
|
||||
<view class="item-right">
|
||||
<text class="item-status">{{ currentLanguage }}</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-item" @click="changeTheme">
|
||||
<text class="item-icon">🎨</text>
|
||||
<text class="item-text">主题设置</text>
|
||||
<view class="item-right">
|
||||
<text class="item-status">{{ currentTheme }}</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 关于我们 -->
|
||||
<view class="settings-section">
|
||||
<text class="section-title">关于我们</text>
|
||||
<view class="section-list">
|
||||
<view class="list-item" @click="aboutUs">
|
||||
<text class="item-icon">ℹ️</text>
|
||||
<text class="item-text">关于商城</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="userAgreement">
|
||||
<text class="item-icon">📜</text>
|
||||
<text class="item-text">用户协议</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="privacyPolicy">
|
||||
<text class="item-icon">🛡️</text>
|
||||
<text class="item-text">隐私政策</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="checkUpdate">
|
||||
<text class="item-icon">🔄</text>
|
||||
<text class="item-text">检查更新</text>
|
||||
<view class="item-right">
|
||||
<text class="item-status">{{ appVersion }}</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 客服与反馈 -->
|
||||
<view class="settings-section">
|
||||
<text class="section-title">客服与反馈</text>
|
||||
<view class="section-list">
|
||||
<view class="list-item" @click="contactService">
|
||||
<text class="item-icon">💬</text>
|
||||
<text class="item-text">联系客服</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="feedback">
|
||||
<text class="item-icon">📝</text>
|
||||
<text class="item-text">意见反馈</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="rateApp">
|
||||
<text class="item-icon">⭐</text>
|
||||
<text class="item-text">给个好评</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<view class="logout-section">
|
||||
<button class="logout-btn" @click="logout">退出登录</button>
|
||||
</view>
|
||||
|
||||
<!-- 账号注销 -->
|
||||
<view class="delete-account-section">
|
||||
<text class="delete-account" @click="deleteAccount">注销账号</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
// import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
type UserType = {
|
||||
id: string
|
||||
phone: string | null
|
||||
email: string | null
|
||||
nickname: string | null
|
||||
avatar_url: string | null
|
||||
}
|
||||
|
||||
type NotificationType = {
|
||||
order: boolean
|
||||
promotion: boolean
|
||||
review: boolean
|
||||
}
|
||||
|
||||
type PrivacyType = {
|
||||
hidePurchase: boolean
|
||||
allowSearchByPhone: boolean
|
||||
receiveMerchantMsg: boolean
|
||||
}
|
||||
|
||||
const userInfo = ref<UserType>({
|
||||
id: '',
|
||||
phone: null,
|
||||
email: null,
|
||||
nickname: null,
|
||||
avatar_url: null
|
||||
})
|
||||
const notifications = ref<NotificationType>({
|
||||
order: true,
|
||||
promotion: true,
|
||||
review: true
|
||||
})
|
||||
const privacy = ref<PrivacyType>({
|
||||
hidePurchase: false,
|
||||
allowSearchByPhone: true,
|
||||
receiveMerchantMsg: true
|
||||
})
|
||||
const cacheSize = ref<string>('0.0 MB')
|
||||
const currentLanguage = ref<string>('简体中文')
|
||||
const currentTheme = ref<string>('自动')
|
||||
const appVersion = ref<string>('1.0.0')
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadUserInfo()
|
||||
loadSettings()
|
||||
})
|
||||
|
||||
// 加载用户信息
|
||||
const loadUserInfo = () => {
|
||||
const userStore = uni.getStorageSync('userInfo')
|
||||
if (userStore) {
|
||||
userInfo.value = userStore
|
||||
}
|
||||
}
|
||||
|
||||
// 加载设置
|
||||
const loadSettings = () => {
|
||||
// 从本地存储加载设置
|
||||
const savedNotifications = uni.getStorageSync('userNotifications')
|
||||
if (savedNotifications) {
|
||||
notifications.value = savedNotifications
|
||||
}
|
||||
|
||||
const savedPrivacy = uni.getStorageSync('userPrivacy')
|
||||
if (savedPrivacy) {
|
||||
privacy.value = savedPrivacy
|
||||
}
|
||||
|
||||
// 计算缓存大小
|
||||
calculateCacheSize()
|
||||
|
||||
// 获取应用版本
|
||||
// @ts-ignore
|
||||
const appInfo = uni.getAppBaseInfo()
|
||||
if (appInfo?.appVersion) {
|
||||
appVersion.value = appInfo.appVersion
|
||||
}
|
||||
}
|
||||
|
||||
// 计算缓存大小
|
||||
const calculateCacheSize = () => {
|
||||
// 这里应该计算实际缓存大小,这里使用模拟数据
|
||||
cacheSize.value = '12.5 MB'
|
||||
}
|
||||
|
||||
// 跳转到个人资料
|
||||
const goToProfile = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/profile'
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到地址管理
|
||||
const goToAddress = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/address'
|
||||
})
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
const changePassword = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/change-password'
|
||||
})
|
||||
}
|
||||
|
||||
// 绑定手机
|
||||
const bindPhone = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/bind-phone'
|
||||
})
|
||||
}
|
||||
|
||||
// 绑定邮箱
|
||||
const bindEmail = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/bind-email'
|
||||
})
|
||||
}
|
||||
|
||||
// 切换通知设置
|
||||
const toggleNotification = (type: keyof NotificationType) => {
|
||||
notifications.value[type] = !notifications.value[type]
|
||||
uni.setStorageSync('userNotifications', notifications.value)
|
||||
}
|
||||
|
||||
// 切换隐私设置
|
||||
const togglePrivacy = (type: keyof PrivacyType) => {
|
||||
privacy.value[type] = !privacy.value[type]
|
||||
uni.setStorageSync('userPrivacy', privacy.value)
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
const clearCache = () => {
|
||||
uni.showModal({
|
||||
title: '清除缓存',
|
||||
content: `确定要清除 ${cacheSize.value} 缓存吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 这里应该清除实际缓存
|
||||
uni.showLoading({
|
||||
title: '清除中...'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
cacheSize.value = '0.0 MB'
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '缓存已清除',
|
||||
icon: 'success'
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 切换语言
|
||||
const changeLanguage = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: ['简体中文', 'English', '日本語'],
|
||||
success: (res) => {
|
||||
const languages = ['简体中文', 'English', '日本語']
|
||||
currentLanguage.value = languages[res.tapIndex]
|
||||
uni.setStorageSync('appLanguage', currentLanguage.value)
|
||||
|
||||
uni.showToast({
|
||||
title: '语言已切换',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
const changeTheme = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: ['自动', '浅色模式', '深色模式'],
|
||||
success: (res) => {
|
||||
const themes = ['自动', '浅色模式', '深色模式']
|
||||
currentTheme.value = themes[res.tapIndex]
|
||||
uni.setStorageSync('appTheme', currentTheme.value)
|
||||
|
||||
uni.showToast({
|
||||
title: '主题已切换',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 关于我们
|
||||
const aboutUs = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/info/about'
|
||||
})
|
||||
}
|
||||
|
||||
// 用户协议
|
||||
const userAgreement = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/terms'
|
||||
})
|
||||
}
|
||||
|
||||
// 隐私政策
|
||||
const privacyPolicy = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/info/privacy'
|
||||
})
|
||||
}
|
||||
|
||||
// 检查更新
|
||||
const checkUpdate = () => {
|
||||
uni.showLoading({
|
||||
title: '检查更新中...'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showModal({
|
||||
title: '检查更新',
|
||||
content: '当前已是最新版本',
|
||||
showCancel: false
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 联系客服
|
||||
const contactService = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/service/chat'
|
||||
})
|
||||
}
|
||||
|
||||
// 意见反馈
|
||||
const feedback = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/info/feedback'
|
||||
})
|
||||
}
|
||||
|
||||
// 给个好评
|
||||
const rateApp = () => {
|
||||
// 这里应该跳转到应用商店评分
|
||||
uni.showModal({
|
||||
title: '给个好评',
|
||||
content: '如果喜欢我们的应用,请给个好评吧!',
|
||||
confirmText: '去评分',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 跳转到应用商店
|
||||
// @ts-ignore
|
||||
uni.navigateToMiniProgram({
|
||||
appId: 'wx1234567890', // 示例AppID
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '跳转失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const logout = () => {
|
||||
uni.showModal({
|
||||
title: '退出登录',
|
||||
content: '确定要退出登录吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
// 调用登出接口
|
||||
/*
|
||||
const { error } = await supa.auth.signOut()
|
||||
|
||||
if (error !== null) {
|
||||
console.error('登出失败:', error)
|
||||
uni.showToast({
|
||||
title: '登出失败',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
// 清除本地存储
|
||||
uni.removeStorageSync('userInfo')
|
||||
uni.removeStorageSync('token')
|
||||
uni.removeStorageSync('userSettings')
|
||||
|
||||
// 跳转到登录页
|
||||
uni.reLaunch({
|
||||
url: '/pages/user/login'
|
||||
})
|
||||
|
||||
} catch (err) {
|
||||
console.error('登出异常:', err)
|
||||
uni.showToast({
|
||||
title: '登出失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 注销账号
|
||||
const deleteAccount = () => {
|
||||
uni.showModal({
|
||||
title: '注销账号',
|
||||
content: '确定要注销账号吗?此操作不可恢复,所有数据将被删除!',
|
||||
confirmText: '注销',
|
||||
confirmColor: '#ff4757',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({
|
||||
title: '处理中...'
|
||||
})
|
||||
|
||||
try {
|
||||
const userId = userInfo.value.id
|
||||
|
||||
// 这里应该调用注销账号的API
|
||||
/*
|
||||
const { error } = await supa
|
||||
.from('users')
|
||||
.update({ status: 0 }) // 标记为注销状态
|
||||
.eq('id', userId)
|
||||
|
||||
if (error !== null) {
|
||||
throw error
|
||||
}
|
||||
*/
|
||||
|
||||
// 清除本地存储
|
||||
uni.removeStorageSync('userInfo')
|
||||
uni.removeStorageSync('token')
|
||||
|
||||
// 提示并跳转
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '账号已注销',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/user/login'
|
||||
})
|
||||
}, 1500)
|
||||
|
||||
} catch (err) {
|
||||
uni.hideLoading()
|
||||
console.error('注销账号失败:', err)
|
||||
uni.showToast({
|
||||
title: '注销失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 24px;
|
||||
color: #333333;
|
||||
padding: 5px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
background-color: #ffffff;
|
||||
margin-bottom: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.item-arrow {
|
||||
color: #999999;
|
||||
font-size: 16px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.item-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-status {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.item-status.bound {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.item-cache {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.logout-section {
|
||||
background-color: #ffffff;
|
||||
margin-top: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
background-color: #ffffff;
|
||||
color: #ff4757;
|
||||
height: 45px;
|
||||
border: 1px solid #ff4757;
|
||||
border-radius: 22.5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.delete-account-section {
|
||||
background-color: #ffffff;
|
||||
padding: 20px 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.delete-account {
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
@@ -17,7 +17,7 @@
|
||||
<text class="item-text">个人资料</text>
|
||||
<text class="item-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="goToAddress">
|
||||
<view class="list-item" @click="goToAddressList">
|
||||
<text class="item-icon">📍</text>
|
||||
<text class="item-text">收货地址</text>
|
||||
<text class="item-arrow">›</text>
|
||||
@@ -192,7 +192,7 @@
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
// import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
type UserType = {
|
||||
id: string
|
||||
@@ -288,9 +288,9 @@ const goToProfile = () => {
|
||||
}
|
||||
|
||||
// 跳转到地址管理
|
||||
const goToAddress = () => {
|
||||
const goToAddressList = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/address'
|
||||
url: '/pages/mall/consumer/address-list'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -471,6 +471,7 @@ const logout = () => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
// 调用登出接口
|
||||
/*
|
||||
const { error } = await supa.auth.signOut()
|
||||
|
||||
if (error !== null) {
|
||||
@@ -481,6 +482,7 @@ const logout = () => {
|
||||
})
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
// 清除本地存储
|
||||
uni.removeStorageSync('userInfo')
|
||||
@@ -521,6 +523,7 @@ const deleteAccount = () => {
|
||||
const userId = userInfo.value.id
|
||||
|
||||
// 这里应该调用注销账号的API
|
||||
/*
|
||||
const { error } = await supa
|
||||
.from('users')
|
||||
.update({ status: 0 }) // 标记为注销状态
|
||||
@@ -529,6 +532,7 @@ const deleteAccount = () => {
|
||||
if (error !== null) {
|
||||
throw error
|
||||
}
|
||||
*/
|
||||
|
||||
// 清除本地存储
|
||||
uni.removeStorageSync('userInfo')
|
||||
@@ -568,6 +572,51 @@ const goBack = () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 响应式布局优化 */
|
||||
@media screen and (min-width: 768px) {
|
||||
.settings-content {
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* 电脑端横向排列部分内容 */
|
||||
.section-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.logout-section, .delete-account-section {
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
.settings-page {
|
||||
flex-direction: row; /* 大屏下改为横向布局,左侧导航,右侧内容 */
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
display: none; /* 大屏下隐藏顶部栏,可能使用侧边栏或其他导航 */
|
||||
}
|
||||
|
||||
/* 这里只是简单示例,实际可能需要更复杂的布局调整 */
|
||||
.settings-content {
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
950
pages/mall/consumer/wallet - 副本.uvue
Normal file
950
pages/mall/consumer/wallet - 副本.uvue
Normal file
@@ -0,0 +1,950 @@
|
||||
<!-- 钱包页面 -->
|
||||
<template>
|
||||
<view class="wallet-page">
|
||||
<!-- 顶部栏 -->
|
||||
<view class="wallet-header">
|
||||
<text class="back-btn" @click="goBack">‹</text>
|
||||
<text class="header-title">我的钱包</text>
|
||||
<text class="more-btn" @click="showMoreActions">···</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="wallet-content" scroll-y>
|
||||
<!-- 余额概览 -->
|
||||
<view class="balance-overview">
|
||||
<text class="balance-label">账户余额</text>
|
||||
<text class="balance-value">¥{{ balance.toFixed(2) }}</text>
|
||||
<view class="balance-actions">
|
||||
<button class="action-btn recharge" @click="recharge">充值</button>
|
||||
<button class="action-btn withdraw" @click="withdraw">提现</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资产统计 -->
|
||||
<view class="assets-stats">
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">累计充值</text>
|
||||
<text class="stat-value">¥{{ stats.totalRecharge.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">累计消费</text>
|
||||
<text class="stat-value">¥{{ stats.totalConsume.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">累计提现</text>
|
||||
<text class="stat-value">¥{{ stats.totalWithdraw.toFixed(2) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快捷功能 -->
|
||||
<view class="quick-actions">
|
||||
<view class="action-grid">
|
||||
<view class="action-item" @click="goToCoupons">
|
||||
<text class="action-icon">🎫</text>
|
||||
<text class="action-text">优惠券</text>
|
||||
</view>
|
||||
<view class="action-item" @click="goToRedPackets">
|
||||
<text class="action-icon">🧧</text>
|
||||
<text class="action-text">红包</text>
|
||||
</view>
|
||||
<view class="action-item" @click="goToPoints">
|
||||
<text class="action-icon">⭐</text>
|
||||
<text class="action-text">积分</text>
|
||||
</view>
|
||||
<view class="action-item" @click="goToBankCards">
|
||||
<text class="action-icon">💳</text>
|
||||
<text class="action-text">银行卡</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 交易记录 -->
|
||||
<view class="transactions-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">交易记录</text>
|
||||
<view class="filter-tabs">
|
||||
<text :class="['filter-tab', { active: activeFilter === 'all' }]"
|
||||
@click="changeFilter('all')">全部</text>
|
||||
<text :class="['filter-tab', { active: activeFilter === 'income' }]"
|
||||
@click="changeFilter('income')">收入</text>
|
||||
<text :class="['filter-tab', { active: activeFilter === 'expense' }]"
|
||||
@click="changeFilter('expense')">支出</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="transactions.length === 0 && !isLoading" class="empty-transactions">
|
||||
<text class="empty-icon">💰</text>
|
||||
<text class="empty-text">暂无交易记录</text>
|
||||
<text class="empty-subtext">快去使用钱包功能吧</text>
|
||||
</view>
|
||||
|
||||
<!-- 交易列表 -->
|
||||
<view class="transactions-list">
|
||||
<view v-for="transaction in transactions"
|
||||
:key="transaction.id"
|
||||
class="transaction-item">
|
||||
<view class="transaction-left">
|
||||
<text class="transaction-icon">{{ getTransactionIcon(transaction.type) }}</text>
|
||||
<view class="transaction-info">
|
||||
<text class="transaction-title">{{ getTransactionTitle(transaction.type) }}</text>
|
||||
<text class="transaction-time">{{ formatTime(transaction.created_at) }}</text>
|
||||
<text v-if="transaction.remark" class="transaction-remark">{{ transaction.remark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="transaction-right">
|
||||
<text :class="['transaction-amount',
|
||||
{ income: transaction.amount > 0, expense: transaction.amount < 0 }]">
|
||||
{{ transaction.amount > 0 ? '+' : '' }}¥{{ Math.abs(transaction.amount).toFixed(2) }}
|
||||
</text>
|
||||
<text class="transaction-balance">余额: ¥{{ transaction.current_balance.toFixed(2) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="isLoading" class="loading-more">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
<view v-if="!hasMore && transactions.length > 0" class="no-more">
|
||||
<text class="no-more-text">没有更多记录了</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 安全提示 -->
|
||||
<view class="security-tips">
|
||||
<text class="tip-title">安全提示</text>
|
||||
<text class="tip-item">1. 请妥善保管您的支付密码</text>
|
||||
<text class="tip-item">2. 不要向他人透露您的账户信息</text>
|
||||
<text class="tip-item">3. 定期修改密码以确保账户安全</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 充值弹窗 -->
|
||||
<view v-if="showRechargePopup" class="recharge-popup">
|
||||
<view class="popup-mask" @click="closeRechargePopup"></view>
|
||||
<view class="popup-content">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">充值</text>
|
||||
<text class="popup-close" @click="closeRechargePopup">×</text>
|
||||
</view>
|
||||
<view class="popup-body">
|
||||
<text class="amount-label">充值金额</text>
|
||||
<view class="amount-input">
|
||||
<text class="currency-symbol">¥</text>
|
||||
<input class="amount-field"
|
||||
v-model="rechargeAmount"
|
||||
type="number"
|
||||
placeholder="请输入充值金额"
|
||||
focus />
|
||||
</view>
|
||||
<view class="quick-amounts">
|
||||
<text v-for="amount in quickAmounts"
|
||||
:key="amount"
|
||||
:class="['quick-amount', { active: rechargeAmount === amount.toString() }]"
|
||||
@click="selectQuickAmount(amount)">
|
||||
¥{{ amount }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="recharge-tip">单笔充值最低10元,最高5000元</text>
|
||||
</view>
|
||||
<view class="popup-footer">
|
||||
<button class="cancel-btn" @click="closeRechargePopup">取消</button>
|
||||
<button class="confirm-btn"
|
||||
:class="{ disabled: !canRecharge }"
|
||||
@click="confirmRecharge">
|
||||
确认充值
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
type WalletType = {
|
||||
id: string
|
||||
user_id: string
|
||||
balance: number
|
||||
total_recharge: number
|
||||
total_consume: number
|
||||
total_withdraw: number
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
type TransactionType = {
|
||||
id: string
|
||||
user_id: string
|
||||
change_amount: number
|
||||
current_balance: number
|
||||
change_type: string // 'recharge' | 'consume' | 'withdraw' | 'refund' | 'reward'
|
||||
related_id: string | null
|
||||
remark: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
type StatsType = {
|
||||
totalRecharge: number
|
||||
totalConsume: number
|
||||
totalWithdraw: number
|
||||
}
|
||||
|
||||
const balance = ref<number>(0)
|
||||
const stats = ref<StatsType>({
|
||||
totalRecharge: 0,
|
||||
totalConsume: 0,
|
||||
totalWithdraw: 0
|
||||
})
|
||||
const transactions = ref<Array<TransactionType>>([])
|
||||
const activeFilter = ref<string>('all')
|
||||
const isLoading = ref<boolean>(false)
|
||||
const currentPage = ref<number>(1)
|
||||
const pageSize = ref<number>(20)
|
||||
const hasMore = ref<boolean>(true)
|
||||
const showRechargePopup = ref<boolean>(false)
|
||||
const rechargeAmount = ref<string>('')
|
||||
const quickAmounts = [50, 100, 200, 500, 1000]
|
||||
|
||||
// 计算属性
|
||||
const canRecharge = computed(() => {
|
||||
const amount = parseFloat(rechargeAmount.value)
|
||||
return !isNaN(amount) && amount >= 10 && amount <= 5000
|
||||
})
|
||||
|
||||
// 监听过滤器变化
|
||||
watch(activeFilter, () => {
|
||||
resetTransactions()
|
||||
loadTransactions()
|
||||
})
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadWalletData()
|
||||
})
|
||||
|
||||
// 重置交易记录
|
||||
const resetTransactions = () => {
|
||||
transactions.value = []
|
||||
currentPage.value = 1
|
||||
hasMore.value = true
|
||||
}
|
||||
|
||||
// 加载钱包数据
|
||||
const loadWalletData = async () => {
|
||||
const userId = getCurrentUserId()
|
||||
if (!userId) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/login'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
loadBalance(),
|
||||
loadTransactions()
|
||||
])
|
||||
}
|
||||
|
||||
// 加载余额信息
|
||||
const loadBalance = async () => {
|
||||
const userId = getCurrentUserId()
|
||||
if (!userId) return
|
||||
|
||||
try {
|
||||
const { data, error } = await supa
|
||||
.from('user_wallets')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.single()
|
||||
|
||||
if (error !== null) {
|
||||
console.error('加载钱包失败:', error)
|
||||
return
|
||||
}
|
||||
|
||||
if (data) {
|
||||
balance.value = data.balance || 0
|
||||
stats.value = {
|
||||
totalRecharge: data.total_recharge || 0,
|
||||
totalConsume: data.total_consume || 0,
|
||||
totalWithdraw: data.total_withdraw || 0
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载钱包异常:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载交易记录
|
||||
const loadTransactions = async (loadMore: boolean = false) => {
|
||||
if (isLoading.value || (!hasMore.value && loadMore)) {
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
const userId = getCurrentUserId()
|
||||
if (!userId) return
|
||||
|
||||
const page = loadMore ? currentPage.value + 1 : 1
|
||||
|
||||
let query = supa
|
||||
.from('balance_records')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false })
|
||||
|
||||
// 根据过滤器筛选
|
||||
if (activeFilter.value === 'income') {
|
||||
query = query.gt('change_amount', 0)
|
||||
} else if (activeFilter.value === 'expense') {
|
||||
query = query.lt('change_amount', 0)
|
||||
}
|
||||
|
||||
// 分页
|
||||
query = query.range((page - 1) * pageSize.value, page * pageSize.value - 1)
|
||||
|
||||
const { data, error } = await query
|
||||
|
||||
if (error !== null) {
|
||||
console.error('加载交易记录失败:', error)
|
||||
return
|
||||
}
|
||||
|
||||
const newTransactions = data || []
|
||||
|
||||
if (loadMore) {
|
||||
transactions.value.push(...newTransactions)
|
||||
currentPage.value = page
|
||||
} else {
|
||||
transactions.value = newTransactions
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
hasMore.value = newTransactions.length === pageSize.value
|
||||
} catch (err) {
|
||||
console.error('加载交易记录异常:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
const getCurrentUserId = (): string => {
|
||||
const userStore = uni.getStorageSync('userInfo')
|
||||
return userStore?.id || ''
|
||||
}
|
||||
|
||||
// 获取交易图标
|
||||
const getTransactionIcon = (type: string): string => {
|
||||
const icons: Record<string, string> = {
|
||||
recharge: '💳',
|
||||
consume: '🛒',
|
||||
withdraw: '🏦',
|
||||
refund: '🔄',
|
||||
reward: '🎁',
|
||||
income: '💰',
|
||||
expense: '📤'
|
||||
}
|
||||
return icons[type] || '💰'
|
||||
}
|
||||
|
||||
// 获取交易标题
|
||||
const getTransactionTitle = (type: string): string => {
|
||||
const titles: Record<string, string> = {
|
||||
recharge: '账户充值',
|
||||
consume: '商品消费',
|
||||
withdraw: '余额提现',
|
||||
refund: '订单退款',
|
||||
reward: '活动奖励',
|
||||
income: '收入',
|
||||
expense: '支出'
|
||||
}
|
||||
return titles[type] || '交易'
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr: string): string => {
|
||||
const date = new Date(timeStr)
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
const hours = date.getHours().toString().padStart(2, '0')
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0')
|
||||
return `${month}-${day} ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
// 显示更多操作
|
||||
const showMoreActions = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: ['交易记录', '安全设置', '帮助中心'],
|
||||
success: (res) => {
|
||||
switch (res.tapIndex) {
|
||||
case 0:
|
||||
// 交易记录已经在当前页
|
||||
break
|
||||
case 1:
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/settings'
|
||||
})
|
||||
break
|
||||
case 2:
|
||||
uni.navigateTo({
|
||||
url: '/pages/info/help'
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 充值
|
||||
const recharge = () => {
|
||||
showRechargePopup.value = true
|
||||
rechargeAmount.value = ''
|
||||
}
|
||||
|
||||
// 提现
|
||||
const withdraw = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/withdraw'
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到优惠券
|
||||
const goToCoupons = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/coupons'
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到红包
|
||||
const goToRedPackets = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/red-packets'
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到积分
|
||||
const goToPoints = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/points'
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到银行卡
|
||||
const goToBankCards = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/bank-cards'
|
||||
})
|
||||
}
|
||||
|
||||
// 切换过滤器
|
||||
const changeFilter = (filter: string) => {
|
||||
activeFilter.value = filter
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const loadMore = () => {
|
||||
if (hasMore.value && !isLoading.value) {
|
||||
loadTransactions(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 选择快捷金额
|
||||
const selectQuickAmount = (amount: number) => {
|
||||
rechargeAmount.value = amount.toString()
|
||||
}
|
||||
|
||||
// 确认充值
|
||||
const confirmRecharge = async () => {
|
||||
if (!canRecharge.value) return
|
||||
|
||||
const amount = parseFloat(rechargeAmount.value)
|
||||
if (isNaN(amount)) return
|
||||
|
||||
// 这里应该跳转到支付页面进行充值
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/payment?type=recharge&amount=${amount}`
|
||||
})
|
||||
|
||||
closeRechargePopup()
|
||||
}
|
||||
|
||||
// 关闭充值弹窗
|
||||
const closeRechargePopup = () => {
|
||||
showRechargePopup.value = false
|
||||
rechargeAmount.value = ''
|
||||
}
|
||||
|
||||
// 返回
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wallet-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.wallet-header {
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 24px;
|
||||
color: #333333;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
color: #333333;
|
||||
font-size: 20px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.wallet-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.balance-overview {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 30px 20px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.balance-value {
|
||||
display: block;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.balance-actions {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn.recharge {
|
||||
background-color: #ffffff;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.action-btn.withdraw {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
color: #ffffff;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.assets-stats {
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
background-color: #ffffff;
|
||||
margin-top: 10px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.action-grid {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 28px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.transactions-section {
|
||||
background-color: #ffffff;
|
||||
margin-top: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
padding: 5px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.filter-tab.active {
|
||||
color: #007aff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.filter-tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background-color: #007aff;
|
||||
}
|
||||
|
||||
.empty-transactions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 60px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
color: #666666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.empty-subtext {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.transactions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.transaction-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.transaction-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.transaction-left {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.transaction-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.transaction-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.transaction-title {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.transaction-time {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.transaction-remark {
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.transaction-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.transaction-amount {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.transaction-amount.income {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.transaction-amount.expense {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.transaction-balance {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.loading-more,
|
||||
.no-more {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-text,
|
||||
.no-more-text {
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.security-tips {
|
||||
background-color: #ffffff;
|
||||
margin-top: 10px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.tip-title {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tip-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.recharge-popup {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.popup-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #ffffff;
|
||||
border-top-left-radius: 15px;
|
||||
border-top-right-radius: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.popup-close {
|
||||
font-size: 24px;
|
||||
color: #999999;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.amount-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.amount-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.currency-symbol {
|
||||
font-size: 20px;
|
||||
color: #333333;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.amount-field {
|
||||
flex: 1;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.quick-amounts {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.quick-amount {
|
||||
padding: 8px 15px;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.quick-amount.active {
|
||||
background-color: #007aff;
|
||||
color: #ffffff;
|
||||
border-color: #007aff;
|
||||
}
|
||||
|
||||
.recharge-tip {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.popup-footer {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.cancel-btn,
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
height: 45px;
|
||||
border-radius: 22.5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #007aff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.confirm-btn.disabled {
|
||||
background-color: #cccccc;
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
@@ -486,6 +486,54 @@ const goBack = () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 响应式布局优化 */
|
||||
@media screen and (min-width: 768px) {
|
||||
.wallet-content {
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.balance-overview {
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.assets-stats, .quick-actions, .transactions-section, .security-tips {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 400px;
|
||||
left: 50%;
|
||||
bottom: 50%;
|
||||
transform: translate(-50%, 50%);
|
||||
border-radius: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
.wallet-page {
|
||||
flex-direction: row; /* 大屏下改为横向布局 */
|
||||
}
|
||||
|
||||
.wallet-header {
|
||||
display: none; /* 大屏下隐藏顶部栏 */
|
||||
}
|
||||
|
||||
.wallet-content {
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user