继续完善
This commit is contained in:
@@ -53,6 +53,7 @@
|
|||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { ref, reactive, computed } from 'vue'
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||||
|
|
||||||
type Address = {
|
type Address = {
|
||||||
id: string
|
id: string
|
||||||
@@ -94,9 +95,39 @@ onLoad((options) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadAddress = (id: string) => {
|
const loadAddress = async (id: string) => {
|
||||||
|
try {
|
||||||
|
// 从Supabase加载地址详情
|
||||||
|
const address = await supabaseService.getAddressById(id)
|
||||||
|
if (address) {
|
||||||
|
formData.name = address.recipient_name
|
||||||
|
formData.phone = address.phone
|
||||||
|
formData.detail = address.detail_address
|
||||||
|
formData.isDefault = address.is_default
|
||||||
|
formData.label = address.label || ''
|
||||||
|
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
|
||||||
|
} else {
|
||||||
|
// 如果Supabase没有找到,尝试从本地存储加载
|
||||||
const storedAddresses = uni.getStorageSync('addresses')
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
if (storedAddresses) {
|
if (storedAddresses) {
|
||||||
|
const addresses = JSON.parse(storedAddresses as string) as Address[]
|
||||||
|
const localAddress = addresses.find(item => item.id === id)
|
||||||
|
if (localAddress) {
|
||||||
|
formData.name = localAddress.name
|
||||||
|
formData.phone = localAddress.phone
|
||||||
|
formData.detail = localAddress.detail
|
||||||
|
formData.isDefault = localAddress.isDefault
|
||||||
|
formData.label = localAddress.label || ''
|
||||||
|
regionString.value = `${localAddress.province} ${localAddress.city} ${localAddress.district}`.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载地址详情失败:', error)
|
||||||
|
// 失败时从本地存储加载
|
||||||
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
|
if (storedAddresses) {
|
||||||
|
try {
|
||||||
const addresses = JSON.parse(storedAddresses as string) as Address[]
|
const addresses = JSON.parse(storedAddresses as string) as Address[]
|
||||||
const address = addresses.find(item => item.id === id)
|
const address = addresses.find(item => item.id === id)
|
||||||
if (address) {
|
if (address) {
|
||||||
@@ -107,6 +138,10 @@ const loadAddress = (id: string) => {
|
|||||||
formData.label = address.label || ''
|
formData.label = address.label || ''
|
||||||
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
|
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析本地地址数据失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +157,7 @@ const onSwitchChange = (e: UniSwitchChangeEvent) => {
|
|||||||
formData.isDefault = e.detail.value
|
formData.isDefault = e.detail.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveAddress = () => {
|
const saveAddress = async () => {
|
||||||
if (!formData.name) {
|
if (!formData.name) {
|
||||||
uni.showToast({ title: '请填写收货人', icon: 'none' })
|
uni.showToast({ title: '请填写收货人', icon: 'none' })
|
||||||
return
|
return
|
||||||
@@ -146,6 +181,31 @@ const saveAddress = () => {
|
|||||||
const city = regions[1] || ''
|
const city = regions[1] || ''
|
||||||
const district = regions.slice(2).join(' ') || ''
|
const district = regions.slice(2).join(' ') || ''
|
||||||
|
|
||||||
|
// 构建地址对象
|
||||||
|
const addressData = {
|
||||||
|
recipient_name: formData.name,
|
||||||
|
phone: formData.phone,
|
||||||
|
province: province,
|
||||||
|
city: city,
|
||||||
|
district: district,
|
||||||
|
detail_address: formData.detail,
|
||||||
|
postal_code: '', // 如果需要可以添加邮政编码字段
|
||||||
|
is_default: formData.isDefault,
|
||||||
|
label: formData.label || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
let success = false
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
// 更新地址
|
||||||
|
success = await supabaseService.updateAddress(addressId.value, addressData)
|
||||||
|
} else {
|
||||||
|
// 添加新地址
|
||||||
|
success = await supabaseService.addAddress(addressData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 同时更新本地存储作为缓存
|
||||||
const storedAddresses = uni.getStorageSync('addresses')
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
let addresses: Address[] = []
|
let addresses: Address[] = []
|
||||||
if (storedAddresses) {
|
if (storedAddresses) {
|
||||||
@@ -180,7 +240,7 @@ const saveAddress = () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const newAddress: Address = {
|
const newAddress: Address = {
|
||||||
id: `addr_${Date.now()}`,
|
id: `addr_${Date.now()}`, // 临时ID,实际由Supabase生成
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
phone: formData.phone,
|
phone: formData.phone,
|
||||||
province: province,
|
province: province,
|
||||||
@@ -190,10 +250,6 @@ const saveAddress = () => {
|
|||||||
isDefault: formData.isDefault,
|
isDefault: formData.isDefault,
|
||||||
label: formData.label
|
label: formData.label
|
||||||
}
|
}
|
||||||
// 如果是第一个地址,自动设为默认
|
|
||||||
if (addresses.length === 0) {
|
|
||||||
newAddress.isDefault = true
|
|
||||||
}
|
|
||||||
addresses.push(newAddress)
|
addresses.push(newAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +263,13 @@ const saveAddress = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
}, 1500)
|
}, 1500)
|
||||||
|
} else {
|
||||||
|
console.error('保存地址失败')
|
||||||
|
uni.showToast({
|
||||||
|
title: '保存失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseSmartInput = () => {
|
const parseSmartInput = () => {
|
||||||
@@ -244,17 +307,23 @@ const parseSmartInput = () => {
|
|||||||
formData.detail = addrText
|
formData.detail = addrText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const deleteAddress = () => {
|
const deleteAddress = async () => {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '确定要删除该地址吗?',
|
content: '确定要删除该地址吗?',
|
||||||
success: (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
|
// 调用Supabase服务删除地址
|
||||||
|
const success = await supabaseService.deleteAddress(addressId.value)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 同时从本地存储中移除
|
||||||
const storedAddresses = uni.getStorageSync('addresses')
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
if (storedAddresses) {
|
if (storedAddresses) {
|
||||||
let addresses = JSON.parse(storedAddresses as string) as Address[]
|
let addresses = JSON.parse(storedAddresses as string) as Address[]
|
||||||
addresses = addresses.filter(item => item.id !== addressId.value)
|
addresses = addresses.filter(item => item.id !== addressId.value)
|
||||||
uni.setStorageSync('addresses', JSON.stringify(addresses))
|
uni.setStorageSync('addresses', JSON.stringify(addresses))
|
||||||
|
}
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '删除成功',
|
title: '删除成功',
|
||||||
@@ -264,6 +333,12 @@ const deleteAddress = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
}, 1500)
|
}, 1500)
|
||||||
|
} else {
|
||||||
|
console.error('删除地址失败')
|
||||||
|
uni.showToast({
|
||||||
|
title: '删除失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
219
pages/mall/consumer/address-list copy.uvue
Normal file
219
pages/mall/consumer/address-list copy.uvue
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<template>
|
||||||
|
<view class="address-list-page">
|
||||||
|
<view class="address-list">
|
||||||
|
<view v-if="addresses.length === 0" class="empty-state">
|
||||||
|
<text class="empty-icon">📍</text>
|
||||||
|
<text class="empty-text">暂无收货地址</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else v-for="(item, index) in addresses" :key="item.id" class="address-item" @click="selectAddress(item)">
|
||||||
|
<view class="item-content">
|
||||||
|
<view class="item-header">
|
||||||
|
<text class="user-name">{{ item.name }}</text>
|
||||||
|
<text class="user-phone">{{ item.phone }}</text>
|
||||||
|
<text v-if="item.isDefault" class="default-tag">默认</text>
|
||||||
|
<text v-if="item.label" class="label-tag">{{ item.label }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="address-text">{{ getFullAddress(item) }}</text>
|
||||||
|
</view>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<view class="footer-btn">
|
||||||
|
<button class="add-btn" @click="addAddress">新建收货地址</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="uts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
|
|
||||||
|
type Address = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
phone: string
|
||||||
|
province: string
|
||||||
|
city: string
|
||||||
|
district: string
|
||||||
|
detail: string
|
||||||
|
isDefault: boolean
|
||||||
|
label?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const addresses = ref<Address[]>([])
|
||||||
|
const selectionMode = ref<boolean>(false)
|
||||||
|
let openerEventChannel: any = null
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
loadAddresses()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
try {
|
||||||
|
const ec = uni.getOpenerEventChannel()
|
||||||
|
openerEventChannel = ec
|
||||||
|
ec?.on('setSelectMode', (data: any) => {
|
||||||
|
if (data && typeof data.selectMode === 'boolean') {
|
||||||
|
selectionMode.value = data.selectMode
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const loadAddresses = () => {
|
||||||
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
|
if (storedAddresses) {
|
||||||
|
try {
|
||||||
|
addresses.value = JSON.parse(storedAddresses as string) as Address[]
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse addresses', e)
|
||||||
|
addresses.value = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 初始Mock数据
|
||||||
|
addresses.value = [
|
||||||
|
{
|
||||||
|
id: 'addr_001',
|
||||||
|
name: '张三',
|
||||||
|
phone: '13800138000',
|
||||||
|
province: '北京市',
|
||||||
|
city: '北京市',
|
||||||
|
district: '朝阳区',
|
||||||
|
detail: '三里屯SOHO A座',
|
||||||
|
isDefault: true,
|
||||||
|
label: '公司'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
uni.setStorageSync('addresses', JSON.stringify(addresses.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFullAddress = (item: Address): string => {
|
||||||
|
return `${item.province}${item.city}${item.district} ${item.detail}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const addAddress = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/mall/consumer/address-edit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除地址
|
||||||
|
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}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectAddress = (item: Address) => {
|
||||||
|
if (selectionMode.value && openerEventChannel) {
|
||||||
|
openerEventChannel.emit('addressSelected', {
|
||||||
|
id: item.id,
|
||||||
|
recipient_name: item.name,
|
||||||
|
phone: item.phone,
|
||||||
|
province: item.province,
|
||||||
|
city: item.city,
|
||||||
|
district: item.district,
|
||||||
|
detail: item.detail,
|
||||||
|
is_default: item.isDefault
|
||||||
|
})
|
||||||
|
uni.navigateBack()
|
||||||
|
} else {
|
||||||
|
editAddress(item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.item-actions {
|
||||||
|
padding: 10px;
|
||||||
|
border-left: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; /* 竖向排列图标 */
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-btn {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
padding-bottom: calc(10px + env(safe-area-inset-bottom));
|
||||||
|
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* 居中显示 */
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
background-color: #ff5000;
|
||||||
|
color: white;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
border: none;
|
||||||
|
width: 100%; /* 默认占满 */
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式布局优化 */
|
||||||
|
@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);
|
||||||
|
border-radius: 12px 12px 0 0; /* 桌面端加点圆角更美观 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
width: 300px; /* 桌面端限制宽度 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { onShow } from '@dcloudio/uni-app'
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
|
import { supabaseService, type UserAddress as SupabaseUserAddress } from '@/utils/supabaseService.uts'
|
||||||
|
|
||||||
type Address = {
|
type Address = {
|
||||||
id: string
|
id: string
|
||||||
@@ -71,31 +72,42 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadAddresses = () => {
|
const loadAddresses = async () => {
|
||||||
|
try {
|
||||||
|
// 从Supabase加载地址数据
|
||||||
|
const supabaseAddresses = await supabaseService.getAddresses()
|
||||||
|
|
||||||
|
// 转换数据格式以匹配前端界面
|
||||||
|
const transformedAddresses = supabaseAddresses.map((item: SupabaseUserAddress) => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.recipient_name,
|
||||||
|
phone: item.phone,
|
||||||
|
province: item.province,
|
||||||
|
city: item.city,
|
||||||
|
district: item.district,
|
||||||
|
detail: item.detail_address,
|
||||||
|
isDefault: item.is_default,
|
||||||
|
label: '' // Supabase表没有label字段,可以后续考虑添加或使用其他字段
|
||||||
|
}))
|
||||||
|
|
||||||
|
addresses.value = transformedAddresses
|
||||||
|
|
||||||
|
// 同时更新本地存储作为缓存
|
||||||
|
uni.setStorageSync('addresses', JSON.stringify(addresses.value))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载地址数据失败:', error)
|
||||||
|
// 如果API调用失败,尝试从本地存储加载
|
||||||
const storedAddresses = uni.getStorageSync('addresses')
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
if (storedAddresses) {
|
if (storedAddresses) {
|
||||||
try {
|
try {
|
||||||
addresses.value = JSON.parse(storedAddresses as string) as Address[]
|
addresses.value = JSON.parse(storedAddresses as string) as Address[]
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse addresses', e)
|
console.error('解析地址数据失败', e)
|
||||||
addresses.value = []
|
addresses.value = []
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 初始Mock数据
|
addresses.value = []
|
||||||
addresses.value = [
|
|
||||||
{
|
|
||||||
id: 'addr_001',
|
|
||||||
name: '张三',
|
|
||||||
phone: '13800138000',
|
|
||||||
province: '北京市',
|
|
||||||
city: '北京市',
|
|
||||||
district: '朝阳区',
|
|
||||||
detail: '三里屯SOHO A座',
|
|
||||||
isDefault: true,
|
|
||||||
label: '公司'
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
uni.setStorageSync('addresses', JSON.stringify(addresses.value))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,21 +122,34 @@ const addAddress = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除地址
|
// 删除地址
|
||||||
const deleteAddress = (id: string) => {
|
const deleteAddress = async (id: string) => {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '确定要删除该地址吗?',
|
content: '确定要删除该地址吗?',
|
||||||
success: (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
|
// 调用Supabase服务删除地址
|
||||||
|
const success = await supabaseService.deleteAddress(id)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 从本地列表移除
|
||||||
const index = addresses.value.findIndex(addr => addr.id === id)
|
const index = addresses.value.findIndex(addr => addr.id === id)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
addresses.value.splice(index, 1)
|
addresses.value.splice(index, 1)
|
||||||
|
// 更新本地存储缓存
|
||||||
uni.setStorageSync('addresses', JSON.stringify(addresses.value))
|
uni.setStorageSync('addresses', JSON.stringify(addresses.value))
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '删除成功',
|
title: '删除成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.error('删除地址失败')
|
||||||
|
uni.showToast({
|
||||||
|
title: '删除失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -439,15 +439,18 @@ const addToCart = (product: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查商品是否已存在
|
// 检查商品是否已存在 (使用商品ID匹配,因为推荐商品没有SKU)
|
||||||
const existingItem = currentItems.find((item: any) => item.id === product.id)
|
const existingItem = currentItems.find((item: any) =>
|
||||||
|
item.productId === product.id || item.id === product.id
|
||||||
|
)
|
||||||
|
|
||||||
if (existingItem) {
|
if (existingItem) {
|
||||||
existingItem.quantity++
|
existingItem.quantity++
|
||||||
} else {
|
} else {
|
||||||
// 添加新商品
|
// 添加新商品
|
||||||
currentItems.push({
|
currentItems.push({
|
||||||
id: product.id,
|
id: product.id, // 商品ID(因为没有SKU)
|
||||||
|
productId: product.id, // 同样存储商品ID
|
||||||
shopId: product.shopId || 'shop_recommend',
|
shopId: product.shopId || 'shop_recommend',
|
||||||
shopName: product.shopName || '推荐好物',
|
shopName: product.shopName || '推荐好物',
|
||||||
name: product.name,
|
name: product.name,
|
||||||
@@ -477,7 +480,21 @@ const goShopping = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const navigateToProduct = (product: any) => {
|
const navigateToProduct = (product: any) => {
|
||||||
uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${product.id}` })
|
// 使用productId(如果存在)作为跳转的商品ID,否则使用id
|
||||||
|
const productId = product.productId || product.id
|
||||||
|
// 传递完整的参数,确保商品详情页能正确加载
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.append('id', productId)
|
||||||
|
params.append('productId', productId)
|
||||||
|
params.append('price', product.price?.toString() || '0')
|
||||||
|
// 商品详情页期望的参数名是originalPrice
|
||||||
|
params.append('originalPrice', (product.original_price || product.originalPrice || (product.price * 1.2).toFixed(2))?.toString())
|
||||||
|
params.append('name', encodeURIComponent(product.name || ''))
|
||||||
|
params.append('image', encodeURIComponent(product.image || '/static/product1.jpg'))
|
||||||
|
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/mall/consumer/product-detail?${params.toString()}`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToCheckout = () => {
|
const goToCheckout = () => {
|
||||||
@@ -1090,7 +1107,7 @@ const goToCheckout = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 购物车操作栏样式 */
|
/* 购物车操作栏样式 - 自适应横向排列 */
|
||||||
.cart-action-bar {
|
.cart-action-bar {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
@@ -1104,49 +1121,61 @@ const goToCheckout = () => {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-left, .action-right {
|
.action-left, .action-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-right {
|
.action-right {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0; /* 防止溢出 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部结算栏 */
|
/* 合计信息区域 - 自适应横向排列 */
|
||||||
/*
|
.total-info {
|
||||||
.cart-footer {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 60px;
|
|
||||||
background-color: white;
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
margin-right: 12px;
|
||||||
padding: 0 15px;
|
flex-shrink: 0;
|
||||||
z-index: 900;
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* 复用之前的样式 */
|
|
||||||
.footer-content {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-left {
|
.total-text {
|
||||||
display: flex;
|
font-size: 14px;
|
||||||
align-items: center;
|
color: #333;
|
||||||
|
margin-right: 5px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.total-price {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #ff5000;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 结算按钮 */
|
||||||
|
.checkout-btn, .delete-btn {
|
||||||
|
background-color: #ff5000;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
padding: 8px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
background-color: #ff3b30; /* 红色删除按钮 */
|
||||||
|
padding: 8px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全选区域 */
|
||||||
.select-all {
|
.select-all {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1156,62 +1185,90 @@ const goToCheckout = () => {
|
|||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
white-space: nowrap;
|
||||||
|
|
||||||
.footer-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-info {
|
|
||||||
margin-right: 15px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-text {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-price {
|
|
||||||
font-size: 18px;
|
|
||||||
color: #ff5000;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-btn {
|
|
||||||
background-color: #ff5000;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 25px;
|
|
||||||
padding: 8px 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-btn {
|
|
||||||
background-color: #ff3b30; /* 红色删除按钮 */
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 25px;
|
|
||||||
padding: 8px 25px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 响应式调整 */
|
/* 响应式调整 */
|
||||||
|
/* 手机端小屏幕优化 */
|
||||||
|
@media screen and (max-width: 375px) {
|
||||||
|
.action-bar-content {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-text {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-price {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-btn, .delete-btn {
|
||||||
|
padding: 8px 15px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all-text {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 平板端优化 */
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.cart-action-bar {
|
.cart-action-bar {
|
||||||
margin: 20px auto;
|
margin: 20px auto;
|
||||||
max-width: 95%;
|
max-width: 95%;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar-content {
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-price {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-btn, .delete-btn {
|
||||||
|
padding: 10px 30px;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 桌面端优化 */
|
||||||
@media screen and (min-width: 1024px) {
|
@media screen and (min-width: 1024px) {
|
||||||
.cart-action-bar {
|
.cart-action-bar {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
|
padding: 20px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar-content {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-info {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-text {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-price {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-btn, .delete-btn {
|
||||||
|
padding: 12px 40px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all-text {
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 大屏幕优化 */
|
||||||
@media screen and (min-width: 1400px) {
|
@media screen and (min-width: 1400px) {
|
||||||
.cart-action-bar {
|
.cart-action-bar {
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
|
|||||||
@@ -168,6 +168,7 @@
|
|||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { onShow } from '@dcloudio/uni-app'
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
|
import { supabaseService, type CartItem as SupabaseCartItem } from '@/utils/supabaseService.uts'
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const cartItems = ref<any[]>([])
|
const cartItems = ref<any[]>([])
|
||||||
@@ -269,6 +270,12 @@ const totalPrice = computed(() => {
|
|||||||
.toFixed(2)
|
.toFixed(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 检查店铺是否全选
|
||||||
|
const isShopSelected = (shopId: string) => {
|
||||||
|
const group = cartGroups.value.find(g => g.shopId === shopId)
|
||||||
|
return group ? group.items.every(item => item.selected) : false
|
||||||
|
}
|
||||||
|
|
||||||
const toggleManageMode = () => {
|
const toggleManageMode = () => {
|
||||||
isManageMode.value = !isManageMode.value
|
isManageMode.value = !isManageMode.value
|
||||||
}
|
}
|
||||||
@@ -289,10 +296,34 @@ onShow(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const loadCartData = () => {
|
const loadCartData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
// 从本地存储加载购物车数据
|
try {
|
||||||
|
// 从Supabase加载购物车数据
|
||||||
|
const supabaseCartItems = await supabaseService.getCartItems()
|
||||||
|
|
||||||
|
// 转换数据格式以匹配前端界面
|
||||||
|
const transformedItems = supabaseCartItems.map((item: SupabaseCartItem) => ({
|
||||||
|
id: item.id,
|
||||||
|
shopId: item.shop_id || 'unknown_shop',
|
||||||
|
shopName: item.shop_name || '未知店铺',
|
||||||
|
name: item.product_name || '商品',
|
||||||
|
price: item.product_price || 0,
|
||||||
|
image: item.product_image || '/static/product1.jpg',
|
||||||
|
spec: item.product_specification || '默认规格',
|
||||||
|
quantity: item.quantity || 1,
|
||||||
|
selected: item.selected || false,
|
||||||
|
productId: item.product_id // 保留productId用于后续操作
|
||||||
|
}))
|
||||||
|
|
||||||
|
cartItems.value = transformedItems
|
||||||
|
|
||||||
|
// 加载推荐商品(暂时保持Mock数据)
|
||||||
|
recommendProducts.value = [...mockRecommendProducts]
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载购物车数据失败:', error)
|
||||||
|
// 如果API调用失败,尝试从本地存储加载
|
||||||
const cartData = uni.getStorageSync('cart')
|
const cartData = uni.getStorageSync('cart')
|
||||||
if (cartData) {
|
if (cartData) {
|
||||||
try {
|
try {
|
||||||
@@ -301,36 +332,32 @@ const loadCartData = () => {
|
|||||||
console.error('解析购物车数据失败', e)
|
console.error('解析购物车数据失败', e)
|
||||||
cartItems.value = []
|
cartItems.value = []
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 如果本地没有数据,使用Mock数据(可选,或者直接为空)
|
|
||||||
// 为了演示效果,这里可以保留一部分Mock数据,或者初始化为空
|
|
||||||
// cartItems.value = [...mockCartItems]
|
|
||||||
cartItems.value = []
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
setTimeout(() => {
|
|
||||||
// 模拟推荐商品加载
|
|
||||||
recommendProducts.value = [...mockRecommendProducts]
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}, 500)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听购物车数据变化并保存到本地存储
|
// 商品操作 - 更新选中状态到Supabase
|
||||||
const saveCartData = () => {
|
const toggleSelect = async (itemId: string) => {
|
||||||
uni.setStorageSync('cart', JSON.stringify(cartItems.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 商品操作 - 增加保存逻辑
|
|
||||||
const toggleSelect = (itemId: string) => {
|
|
||||||
const index = cartItems.value.findIndex(item => item.id === itemId)
|
const index = cartItems.value.findIndex(item => item.id === itemId)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
cartItems.value[index].selected = !cartItems.value[index].selected
|
const newSelected = !cartItems.value[index].selected
|
||||||
|
cartItems.value[index].selected = newSelected
|
||||||
cartItems.value = [...cartItems.value] // 触发响应式更新
|
cartItems.value = [...cartItems.value] // 触发响应式更新
|
||||||
saveCartData()
|
|
||||||
|
// 更新到Supabase
|
||||||
|
const success = await supabaseService.updateCartItemSelection(itemId, newSelected)
|
||||||
|
if (!success) {
|
||||||
|
console.error('更新选中状态失败')
|
||||||
|
// 恢复状态
|
||||||
|
cartItems.value[index].selected = !newSelected
|
||||||
|
cartItems.value = [...cartItems.value]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleShopSelect = (shopId: string) => {
|
const toggleShopSelect = async (shopId: string) => {
|
||||||
const group = cartGroups.value.find((g: any) => g.shopId === shopId)
|
const group = cartGroups.value.find((g: any) => g.shopId === shopId)
|
||||||
if (!group) return
|
if (!group) return
|
||||||
|
|
||||||
@@ -338,56 +365,110 @@ const toggleShopSelect = (shopId: string) => {
|
|||||||
const isAllShopSelected = (group.items as any[]).every((item: any) => item.selected)
|
const isAllShopSelected = (group.items as any[]).every((item: any) => item.selected)
|
||||||
const newState = !isAllShopSelected
|
const newState = !isAllShopSelected
|
||||||
|
|
||||||
// 更新该店铺下所有商品的状态
|
// 获取该店铺下所有商品的ID
|
||||||
|
const shopItemIds = cartItems.value
|
||||||
|
.filter(item => item.shopId === shopId)
|
||||||
|
.map(item => item.id)
|
||||||
|
|
||||||
|
// 批量更新到Supabase
|
||||||
|
const success = await supabaseService.batchUpdateCartItemSelection(shopItemIds, newState)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 更新本地状态
|
||||||
cartItems.value.forEach(item => {
|
cartItems.value.forEach(item => {
|
||||||
if (item.shopId === shopId) {
|
if (item.shopId === shopId) {
|
||||||
item.selected = newState
|
item.selected = newState
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
cartItems.value = [...cartItems.value]
|
cartItems.value = [...cartItems.value]
|
||||||
saveCartData()
|
} else {
|
||||||
|
console.error('批量更新店铺商品选中状态失败')
|
||||||
|
uni.showToast({
|
||||||
|
title: '操作失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleSelectAll = () => {
|
const toggleSelectAll = async () => {
|
||||||
const newSelectedState = !allSelected.value
|
const newSelectedState = !allSelected.value
|
||||||
cartItems.value = cartItems.value.map(item => ({
|
const selectedItems = cartItems.value.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
selected: newSelectedState
|
selected: newSelectedState
|
||||||
}))
|
}))
|
||||||
saveCartData()
|
|
||||||
|
// 更新到Supabase
|
||||||
|
const itemIds = cartItems.value.map(item => item.id)
|
||||||
|
const success = await supabaseService.batchUpdateCartItemSelection(itemIds, newSelectedState)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
cartItems.value = selectedItems
|
||||||
|
} else {
|
||||||
|
console.error('批量更新选中状态失败')
|
||||||
|
uni.showToast({
|
||||||
|
title: '操作失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const increaseQuantity = (itemId: string) => {
|
const increaseQuantity = async (itemId: string) => {
|
||||||
const index = cartItems.value.findIndex(item => item.id === itemId)
|
const index = cartItems.value.findIndex(item => item.id === itemId)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
cartItems.value[index].quantity++
|
const newQuantity = cartItems.value[index].quantity + 1
|
||||||
|
cartItems.value[index].quantity = newQuantity
|
||||||
cartItems.value = [...cartItems.value]
|
cartItems.value = [...cartItems.value]
|
||||||
saveCartData()
|
|
||||||
|
// 更新到Supabase
|
||||||
|
const success = await supabaseService.updateCartItemQuantity(itemId, newQuantity)
|
||||||
|
if (!success) {
|
||||||
|
console.error('更新商品数量失败')
|
||||||
|
// 恢复状态
|
||||||
|
cartItems.value[index].quantity = newQuantity - 1
|
||||||
|
cartItems.value = [...cartItems.value]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const decreaseQuantity = (itemId: string) => {
|
const decreaseQuantity = async (itemId: string) => {
|
||||||
const index = cartItems.value.findIndex(item => item.id === itemId)
|
const index = cartItems.value.findIndex(item => item.id === itemId)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
if (cartItems.value[index].quantity > 1) {
|
if (cartItems.value[index].quantity > 1) {
|
||||||
cartItems.value[index].quantity--
|
const newQuantity = cartItems.value[index].quantity - 1
|
||||||
|
cartItems.value[index].quantity = newQuantity
|
||||||
cartItems.value = [...cartItems.value]
|
cartItems.value = [...cartItems.value]
|
||||||
saveCartData()
|
|
||||||
|
// 更新到Supabase
|
||||||
|
const success = await supabaseService.updateCartItemQuantity(itemId, newQuantity)
|
||||||
|
if (!success) {
|
||||||
|
console.error('更新商品数量失败')
|
||||||
|
// 恢复状态
|
||||||
|
cartItems.value[index].quantity = newQuantity + 1
|
||||||
|
cartItems.value = [...cartItems.value]
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 数量为1时,询问是否删除
|
// 数量为1时,询问是否删除
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '确定要从购物车移除该商品吗?',
|
content: '确定要从购物车移除该商品吗?',
|
||||||
success: (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
|
// 从Supabase删除
|
||||||
|
const success = await supabaseService.deleteCartItem(itemId)
|
||||||
|
if (success) {
|
||||||
cartItems.value.splice(index, 1)
|
cartItems.value.splice(index, 1)
|
||||||
cartItems.value = [...cartItems.value]
|
cartItems.value = [...cartItems.value]
|
||||||
saveCartData()
|
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '已移除',
|
title: '已移除',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
console.error('删除商品失败')
|
||||||
|
uni.showToast({
|
||||||
|
title: '删除失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -396,7 +477,7 @@ const decreaseQuantity = (itemId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除商品 - 增加保存逻辑
|
// 删除商品 - 增加保存逻辑
|
||||||
const deleteSelectedItems = () => {
|
const deleteSelectedItems = async () => {
|
||||||
if (selectedCount.value === 0) {
|
if (selectedCount.value === 0) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '请选择要删除的商品',
|
title: '请选择要删除的商品',
|
||||||
@@ -408,10 +489,19 @@ const deleteSelectedItems = () => {
|
|||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: `确定要删除选中的 ${selectedCount.value} 件商品吗?`,
|
content: `确定要删除选中的 ${selectedCount.value} 件商品吗?`,
|
||||||
success: (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
|
// 获取选中的商品ID
|
||||||
|
const selectedItemIds = cartItems.value
|
||||||
|
.filter(item => item.selected)
|
||||||
|
.map(item => item.id)
|
||||||
|
|
||||||
|
// 批量删除到Supabase
|
||||||
|
const success = await supabaseService.batchDeleteCartItems(selectedItemIds)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 从本地列表移除
|
||||||
cartItems.value = cartItems.value.filter(item => !item.selected)
|
cartItems.value = cartItems.value.filter(item => !item.selected)
|
||||||
saveCartData()
|
|
||||||
|
|
||||||
// 如果购物车删空了,退出管理模式
|
// 如果购物车删空了,退出管理模式
|
||||||
if (cartItems.value.length === 0) {
|
if (cartItems.value.length === 0) {
|
||||||
@@ -421,6 +511,13 @@ const deleteSelectedItems = () => {
|
|||||||
title: '删除成功',
|
title: '删除成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
console.error('批量删除商品失败')
|
||||||
|
uni.showToast({
|
||||||
|
title: '删除失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -506,9 +603,6 @@ const goToCheckout = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保最新状态已保存到本地存储
|
|
||||||
saveCartData()
|
|
||||||
|
|
||||||
// 获取选中的商品 (直接过滤cartItems,不依赖cartGroups)
|
// 获取选中的商品 (直接过滤cartItems,不依赖cartGroups)
|
||||||
const selectedItems = cartItems.value
|
const selectedItems = cartItems.value
|
||||||
.filter(item => item.selected)
|
.filter(item => item.selected)
|
||||||
|
|||||||
1733
pages/mall/consumer/checkout copy 2.uvue
Normal file
1733
pages/mall/consumer/checkout copy 2.uvue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -120,7 +120,15 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<scroll-view class="address-list-container" scroll-y>
|
<scroll-view class="address-list-container" scroll-y>
|
||||||
|
<!-- 登录提示 -->
|
||||||
|
<view v-if="!isLoggedIn" class="login-prompt" @click="goToLogin">
|
||||||
|
<text class="login-prompt-icon">🔒</text>
|
||||||
|
<text class="login-prompt-text">您尚未登录,点击登录以同步服务器地址</text>
|
||||||
|
<text class="login-prompt-arrow">›</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 地址列表 -->
|
<!-- 地址列表 -->
|
||||||
|
<view v-if="isLoggedIn">
|
||||||
<view v-if="addressList.length > 0">
|
<view v-if="addressList.length > 0">
|
||||||
<view v-for="address in addressList" :key="address.id"
|
<view v-for="address in addressList" :key="address.id"
|
||||||
class="popup-address-item" @click="handleSelectAddress(address)">
|
class="popup-address-item" @click="handleSelectAddress(address)">
|
||||||
@@ -143,6 +151,32 @@
|
|||||||
<text class="popup-empty-icon">📍</text>
|
<text class="popup-empty-icon">📍</text>
|
||||||
<text class="popup-empty-text">暂无收货地址</text>
|
<text class="popup-empty-text">暂无收货地址</text>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 未登录时的本地地址展示 -->
|
||||||
|
<view v-if="!isLoggedIn && addressList.length > 0">
|
||||||
|
<text class="local-address-title">本地地址(未同步)</text>
|
||||||
|
<view v-for="address in addressList" :key="address.id"
|
||||||
|
class="popup-address-item" @click="handleSelectAddress(address)">
|
||||||
|
<view class="popup-address-header">
|
||||||
|
<text class="popup-address-name">{{ address.recipient_name }}</text>
|
||||||
|
<text class="popup-address-phone">{{ address.phone }}</text>
|
||||||
|
<view v-if="address.is_default" class="popup-default-tag">
|
||||||
|
<text class="popup-tag-text">默认</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<text class="popup-address-detail">{{ getFullAddress(address) }}</text>
|
||||||
|
<view v-if="selectedAddress && selectedAddress.id === address.id" class="popup-selected-indicator">
|
||||||
|
<text>✓</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 完全无地址状态 -->
|
||||||
|
<view v-if="isLoggedIn && addressList.length === 0" class="popup-empty-address">
|
||||||
|
<text class="popup-empty-icon">📍</text>
|
||||||
|
<text class="popup-empty-text">暂无收货地址</text>
|
||||||
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<!-- 新建地址按钮 -->
|
<!-- 新建地址按钮 -->
|
||||||
@@ -241,6 +275,7 @@
|
|||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { ref, onMounted, computed, watch, onUnmounted, getCurrentInstance } from 'vue'
|
import { ref, onMounted, computed, watch, onUnmounted, getCurrentInstance } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { supabaseService, type UserAddress as SupabaseUserAddress } from '@/utils/supabaseService.uts'
|
||||||
|
|
||||||
type CheckoutItemType = {
|
type CheckoutItemType = {
|
||||||
id: string
|
id: string
|
||||||
@@ -467,9 +502,26 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 页面显示时触发
|
||||||
|
const onShow = () => {
|
||||||
|
console.log('checkout页面显示,检查登录状态并重新加载地址')
|
||||||
|
// 检查用户登录状态
|
||||||
|
const userId = getCurrentUserId()
|
||||||
|
if (userId) {
|
||||||
|
console.log('用户已登录,重新加载地址数据')
|
||||||
|
// 重新加载默认地址和地址列表
|
||||||
|
loadDefaultAddress()
|
||||||
|
loadAddressList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册页面显示事件
|
||||||
|
uni.$on('checkoutPageShow', onShow)
|
||||||
|
|
||||||
// 组件卸载时移除事件监听
|
// 组件卸载时移除事件监听
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
uni.$off('addressUpdated')
|
uni.$off('addressUpdated')
|
||||||
|
uni.$off('checkoutPageShow')
|
||||||
// 离开页面时清除结算数据,防止下次进入时显示旧数据
|
// 离开页面时清除结算数据,防止下次进入时显示旧数据
|
||||||
uni.removeStorageSync('checkout_type')
|
uni.removeStorageSync('checkout_type')
|
||||||
uni.removeStorageSync('checkout_items')
|
uni.removeStorageSync('checkout_items')
|
||||||
@@ -523,8 +575,72 @@ const loadCheckoutData = () => {
|
|||||||
|
|
||||||
// 加载默认地址
|
// 加载默认地址
|
||||||
const loadDefaultAddress = async () => {
|
const loadDefaultAddress = async () => {
|
||||||
// 从本地存储加载地址数据
|
try {
|
||||||
|
// 首先检查用户是否登录
|
||||||
|
const currentUserId = getCurrentUserId()
|
||||||
|
console.log('loadDefaultAddress: 当前用户ID:', currentUserId)
|
||||||
|
|
||||||
|
// 如果用户已登录,尝试从Supabase加载地址数据
|
||||||
|
if (currentUserId) {
|
||||||
|
console.log('loadDefaultAddress: 用户已登录,从Supabase加载地址')
|
||||||
|
const supabaseAddresses = await supabaseService.getAddresses()
|
||||||
|
console.log('loadDefaultAddress: Supabase返回地址:', supabaseAddresses)
|
||||||
|
|
||||||
|
if (supabaseAddresses && supabaseAddresses.length > 0) {
|
||||||
|
// 查找默认地址
|
||||||
|
const defaultAddress = supabaseAddresses.find((addr: SupabaseUserAddress) => addr.is_default === true)
|
||||||
|
if (defaultAddress) {
|
||||||
|
// 转换地址格式以匹配selectedAddress的结构
|
||||||
|
selectedAddress.value = {
|
||||||
|
id: defaultAddress.id,
|
||||||
|
recipient_name: defaultAddress.recipient_name,
|
||||||
|
phone: defaultAddress.phone,
|
||||||
|
province: defaultAddress.province,
|
||||||
|
city: defaultAddress.city,
|
||||||
|
district: defaultAddress.district,
|
||||||
|
detail: defaultAddress.detail_address,
|
||||||
|
is_default: defaultAddress.is_default
|
||||||
|
}
|
||||||
|
console.log('loadDefaultAddress: 找到默认地址:', selectedAddress.value)
|
||||||
|
} else {
|
||||||
|
// 如果没有默认地址,使用第一个地址
|
||||||
|
const firstAddress = supabaseAddresses[0]
|
||||||
|
selectedAddress.value = {
|
||||||
|
id: firstAddress.id,
|
||||||
|
recipient_name: firstAddress.recipient_name,
|
||||||
|
phone: firstAddress.phone,
|
||||||
|
province: firstAddress.province,
|
||||||
|
city: firstAddress.city,
|
||||||
|
district: firstAddress.district,
|
||||||
|
detail: firstAddress.detail_address,
|
||||||
|
is_default: firstAddress.is_default
|
||||||
|
}
|
||||||
|
console.log('loadDefaultAddress: 使用第一个地址:', selectedAddress.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同时更新本地存储缓存
|
||||||
|
const localAddresses = supabaseAddresses.map((addr: SupabaseUserAddress) => ({
|
||||||
|
id: addr.id,
|
||||||
|
name: addr.recipient_name,
|
||||||
|
phone: addr.phone,
|
||||||
|
province: addr.province,
|
||||||
|
city: addr.city,
|
||||||
|
district: addr.district,
|
||||||
|
detail: addr.detail_address,
|
||||||
|
isDefault: addr.is_default
|
||||||
|
}))
|
||||||
|
uni.setStorageSync('addresses', JSON.stringify(localAddresses))
|
||||||
|
console.log('loadDefaultAddress: 地址已保存到本地存储')
|
||||||
|
} else {
|
||||||
|
console.log('loadDefaultAddress: Supabase未返回地址数据')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果Supabase没有地址数据或用户未登录,尝试从本地存储加载
|
||||||
|
if (!selectedAddress.value) {
|
||||||
|
console.log('loadDefaultAddress: 尝试从本地存储加载地址')
|
||||||
const storedAddresses = uni.getStorageSync('addresses')
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
|
console.log('loadDefaultAddress: 本地存储地址数据:', storedAddresses)
|
||||||
if (storedAddresses) {
|
if (storedAddresses) {
|
||||||
try {
|
try {
|
||||||
const addresses = JSON.parse(storedAddresses as string) as any[]
|
const addresses = JSON.parse(storedAddresses as string) as any[]
|
||||||
@@ -532,7 +648,97 @@ const loadDefaultAddress = async () => {
|
|||||||
// 查找默认地址
|
// 查找默认地址
|
||||||
const defaultAddress = addresses.find((addr: any) => addr.isDefault === true)
|
const defaultAddress = addresses.find((addr: any) => addr.isDefault === true)
|
||||||
if (defaultAddress) {
|
if (defaultAddress) {
|
||||||
// 转换地址格式以匹配selectedAddress的结构
|
selectedAddress.value = {
|
||||||
|
id: defaultAddress.id,
|
||||||
|
recipient_name: defaultAddress.name,
|
||||||
|
phone: defaultAddress.phone,
|
||||||
|
province: defaultAddress.province,
|
||||||
|
city: defaultAddress.city,
|
||||||
|
district: defaultAddress.district,
|
||||||
|
detail: defaultAddress.detail,
|
||||||
|
is_default: defaultAddress.isDefault
|
||||||
|
}
|
||||||
|
console.log('loadDefaultAddress: 从本地存储找到默认地址:', selectedAddress.value)
|
||||||
|
} else {
|
||||||
|
// 如果没有默认地址,使用第一个地址
|
||||||
|
const firstAddress = addresses[0]
|
||||||
|
selectedAddress.value = {
|
||||||
|
id: firstAddress.id,
|
||||||
|
recipient_name: firstAddress.name,
|
||||||
|
phone: firstAddress.phone,
|
||||||
|
province: firstAddress.province,
|
||||||
|
city: firstAddress.city,
|
||||||
|
district: firstAddress.district,
|
||||||
|
detail: firstAddress.detail,
|
||||||
|
is_default: firstAddress.isDefault
|
||||||
|
}
|
||||||
|
console.log('loadDefaultAddress: 从本地存储使用第一个地址:', selectedAddress.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('解析本地地址数据失败:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果仍然没有地址,使用模拟地址数据
|
||||||
|
if (!selectedAddress.value) {
|
||||||
|
console.log('loadDefaultAddress: 使用模拟地址数据')
|
||||||
|
// 模拟地址数据
|
||||||
|
const mockAddresses = [
|
||||||
|
{
|
||||||
|
id: 'addr_001',
|
||||||
|
name: '张三',
|
||||||
|
phone: '13800138001',
|
||||||
|
province: '北京市',
|
||||||
|
city: '北京市',
|
||||||
|
district: '朝阳区',
|
||||||
|
detail: '建国路88号SOHO现代城A座1001',
|
||||||
|
isDefault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'addr_002',
|
||||||
|
name: '李四',
|
||||||
|
phone: '13900139001',
|
||||||
|
province: '上海市',
|
||||||
|
city: '上海市',
|
||||||
|
district: '浦东新区',
|
||||||
|
detail: '陆家嘴环路1000号汇亚大厦20层',
|
||||||
|
isDefault: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 保存模拟地址到本地存储
|
||||||
|
uni.setStorageSync('addresses', JSON.stringify(mockAddresses))
|
||||||
|
console.log('loadDefaultAddress: 模拟地址已保存到本地存储')
|
||||||
|
|
||||||
|
// 使用第一个地址作为默认地址
|
||||||
|
selectedAddress.value = {
|
||||||
|
id: mockAddresses[0].id,
|
||||||
|
recipient_name: mockAddresses[0].name,
|
||||||
|
phone: mockAddresses[0].phone,
|
||||||
|
province: mockAddresses[0].province,
|
||||||
|
city: mockAddresses[0].city,
|
||||||
|
district: mockAddresses[0].district,
|
||||||
|
detail: mockAddresses[0].detail,
|
||||||
|
is_default: mockAddresses[0].isDefault
|
||||||
|
}
|
||||||
|
console.log('loadDefaultAddress: 使用模拟地址:', selectedAddress.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果仍然没有地址,selectedAddress.value将保持为null
|
||||||
|
// 用户可以在结算页面点击地址区域添加新地址
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('从Supabase加载默认地址失败:', error)
|
||||||
|
// 失败时从本地存储加载
|
||||||
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
|
if (storedAddresses) {
|
||||||
|
try {
|
||||||
|
const addresses = JSON.parse(storedAddresses as string) as any[]
|
||||||
|
if (addresses && addresses.length > 0) {
|
||||||
|
const defaultAddress = addresses.find((addr: any) => addr.isDefault === true)
|
||||||
|
if (defaultAddress) {
|
||||||
selectedAddress.value = {
|
selectedAddress.value = {
|
||||||
id: defaultAddress.id,
|
id: defaultAddress.id,
|
||||||
recipient_name: defaultAddress.name,
|
recipient_name: defaultAddress.name,
|
||||||
@@ -544,7 +750,6 @@ const loadDefaultAddress = async () => {
|
|||||||
is_default: defaultAddress.isDefault
|
is_default: defaultAddress.isDefault
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果没有默认地址,使用第一个地址
|
|
||||||
const firstAddress = addresses[0]
|
const firstAddress = addresses[0]
|
||||||
selectedAddress.value = {
|
selectedAddress.value = {
|
||||||
id: firstAddress.id,
|
id: firstAddress.id,
|
||||||
@@ -559,39 +764,79 @@ const loadDefaultAddress = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('解析地址数据失败:', err)
|
console.error('解析本地地址数据失败:', err)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 如果没有地址数据,尝试使用Mock数据初始化(为了演示效果)
|
|
||||||
const mockAddress = {
|
|
||||||
id: 'addr_mock_default',
|
|
||||||
name: '测试用户',
|
|
||||||
phone: '13800138000',
|
|
||||||
province: '北京市',
|
|
||||||
city: '北京市',
|
|
||||||
district: '朝阳区',
|
|
||||||
detail: '三里屯SOHO A座',
|
|
||||||
isDefault: true
|
|
||||||
}
|
|
||||||
uni.setStorageSync('addresses', JSON.stringify([mockAddress]))
|
|
||||||
selectedAddress.value = {
|
|
||||||
id: mockAddress.id,
|
|
||||||
recipient_name: mockAddress.name,
|
|
||||||
phone: mockAddress.phone,
|
|
||||||
province: mockAddress.province,
|
|
||||||
city: mockAddress.city,
|
|
||||||
district: mockAddress.district,
|
|
||||||
detail: mockAddress.detail,
|
|
||||||
is_default: mockAddress.isDefault
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前用户ID
|
// 获取当前用户ID
|
||||||
const getCurrentUserId = (): string => {
|
const getCurrentUserId = (): string => {
|
||||||
const userStore = uni.getStorageSync('userInfo')
|
// 尝试从多个可能的键名获取用户ID
|
||||||
return userStore?.id || ''
|
const possibleKeys = ['user_id', 'userId', 'uid', 'user_uuid', 'userID', 'user.id']
|
||||||
|
|
||||||
|
for (const key of possibleKeys) {
|
||||||
|
const value = uni.getStorageSync(key)
|
||||||
|
console.log(`getCurrentUserId: 尝试键名 ${key}:`, value)
|
||||||
|
if (value) {
|
||||||
|
console.log(`getCurrentUserId: 从 ${key} 获取到用户ID:`, value)
|
||||||
|
return value as string
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试从userInfo对象获取
|
||||||
|
const userInfo = uni.getStorageSync('userInfo')
|
||||||
|
console.log('getCurrentUserId: 从userInfo获取:', userInfo)
|
||||||
|
if (userInfo) {
|
||||||
|
// userInfo可能是字符串(需要解析)或对象
|
||||||
|
let userInfoObj: any = userInfo
|
||||||
|
if (typeof userInfo === 'string') {
|
||||||
|
try {
|
||||||
|
userInfoObj = JSON.parse(userInfo)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析userInfo失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试多个可能的属性名
|
||||||
|
const possibleProps = ['id', 'userId', 'uid', 'user_id', 'uuid', 'user_uuid']
|
||||||
|
for (const prop of possibleProps) {
|
||||||
|
if (userInfoObj && userInfoObj[prop]) {
|
||||||
|
console.log(`getCurrentUserId: 从userInfo.${prop} 获取到用户ID:`, userInfoObj[prop])
|
||||||
|
return userInfoObj[prop] as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试从auth获取(如果使用Supabase Auth)
|
||||||
|
const authData = uni.getStorageSync('supabase.auth.token')
|
||||||
|
if (authData) {
|
||||||
|
console.log('getCurrentUserId: 从supabase.auth.token获取:', authData)
|
||||||
|
try {
|
||||||
|
const authObj = typeof authData === 'string' ? JSON.parse(authData) : authData
|
||||||
|
if (authObj.currentSession && authObj.currentSession.user && authObj.currentSession.user.id) {
|
||||||
|
console.log('getCurrentUserId: 从auth session获取用户ID:', authObj.currentSession.user.id)
|
||||||
|
return authObj.currentSession.user.id as string
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析auth数据失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印所有存储键,用于调试
|
||||||
|
console.log('getCurrentUserId: 所有Storage键:')
|
||||||
|
const allKeys = uni.getStorageInfoSync().keys
|
||||||
|
console.log('Storage keys:', allKeys)
|
||||||
|
|
||||||
|
console.log('getCurrentUserId: 未找到用户ID')
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户登录状态
|
||||||
|
const isLoggedIn = computed(() => {
|
||||||
|
const userId = getCurrentUserId()
|
||||||
|
return !!userId
|
||||||
|
})
|
||||||
|
|
||||||
// 获取完整地址
|
// 获取完整地址
|
||||||
const getFullAddress = (address: any): string => {
|
const getFullAddress = (address: any): string => {
|
||||||
@@ -600,7 +845,133 @@ const getFullAddress = (address: any): string => {
|
|||||||
|
|
||||||
// 加载地址列表
|
// 加载地址列表
|
||||||
const loadAddressList = async () => {
|
const loadAddressList = async () => {
|
||||||
// 从本地存储加载地址数据
|
try {
|
||||||
|
// 首先检查用户是否登录
|
||||||
|
const currentUserId = getCurrentUserId()
|
||||||
|
console.log('loadAddressList: 当前用户ID:', currentUserId)
|
||||||
|
|
||||||
|
// 如果用户已登录,尝试从Supabase加载地址数据
|
||||||
|
if (currentUserId) {
|
||||||
|
console.log('loadAddressList: 用户已登录,从Supabase加载地址')
|
||||||
|
const supabaseAddresses = await supabaseService.getAddresses()
|
||||||
|
console.log('loadAddressList: Supabase返回地址:', supabaseAddresses)
|
||||||
|
|
||||||
|
if (supabaseAddresses && supabaseAddresses.length > 0) {
|
||||||
|
// 转换地址格式以匹配addressList的结构
|
||||||
|
addressList.value = supabaseAddresses.map((addr: SupabaseUserAddress) => ({
|
||||||
|
id: addr.id,
|
||||||
|
recipient_name: addr.recipient_name,
|
||||||
|
phone: addr.phone,
|
||||||
|
province: addr.province,
|
||||||
|
city: addr.city,
|
||||||
|
district: addr.district,
|
||||||
|
detail: addr.detail_address,
|
||||||
|
is_default: addr.is_default
|
||||||
|
}))
|
||||||
|
console.log('loadAddressList: 从Supabase加载地址成功,数量:', addressList.value.length)
|
||||||
|
|
||||||
|
// 同时更新本地存储缓存
|
||||||
|
const localAddresses = supabaseAddresses.map((addr: SupabaseUserAddress) => ({
|
||||||
|
id: addr.id,
|
||||||
|
name: addr.recipient_name,
|
||||||
|
phone: addr.phone,
|
||||||
|
province: addr.province,
|
||||||
|
city: addr.city,
|
||||||
|
district: addr.district,
|
||||||
|
detail: addr.detail_address,
|
||||||
|
isDefault: addr.is_default
|
||||||
|
}))
|
||||||
|
uni.setStorageSync('addresses', JSON.stringify(localAddresses))
|
||||||
|
console.log('loadAddressList: 地址已保存到本地存储')
|
||||||
|
} else {
|
||||||
|
console.log('loadAddressList: Supabase未返回地址数据')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果Supabase没有地址数据或用户未登录,尝试从本地存储加载
|
||||||
|
if (!addressList.value || addressList.value.length === 0) {
|
||||||
|
console.log('loadAddressList: 尝试从本地存储加载地址')
|
||||||
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
|
console.log('loadAddressList: 本地存储地址数据:', storedAddresses)
|
||||||
|
if (storedAddresses) {
|
||||||
|
try {
|
||||||
|
const addresses = JSON.parse(storedAddresses as string) as any[]
|
||||||
|
if (addresses && addresses.length > 0) {
|
||||||
|
// 转换地址格式以匹配addressList的结构
|
||||||
|
addressList.value = addresses.map((addr: any) => ({
|
||||||
|
id: addr.id,
|
||||||
|
recipient_name: addr.name,
|
||||||
|
phone: addr.phone,
|
||||||
|
province: addr.province,
|
||||||
|
city: addr.city,
|
||||||
|
district: addr.district,
|
||||||
|
detail: addr.detail,
|
||||||
|
is_default: addr.isDefault
|
||||||
|
}))
|
||||||
|
console.log('loadAddressList: 从本地存储加载地址成功,数量:', addressList.value.length)
|
||||||
|
} else {
|
||||||
|
addressList.value = []
|
||||||
|
console.log('loadAddressList: 本地存储地址为空数组')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('解析本地地址数据失败:', err)
|
||||||
|
addressList.value = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addressList.value = []
|
||||||
|
console.log('loadAddressList: 本地存储没有地址数据')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果仍然没有地址,使用模拟地址数据(与loadDefaultAddress保持一致)
|
||||||
|
if (!addressList.value || addressList.value.length === 0) {
|
||||||
|
console.log('loadAddressList: 使用模拟地址数据')
|
||||||
|
// 模拟地址数据(与loadDefaultAddress中保持一致)
|
||||||
|
const mockAddresses = [
|
||||||
|
{
|
||||||
|
id: 'addr_001',
|
||||||
|
name: '张三',
|
||||||
|
phone: '13800138001',
|
||||||
|
province: '北京市',
|
||||||
|
city: '北京市',
|
||||||
|
district: '朝阳区',
|
||||||
|
detail: '建国路88号SOHO现代城A座1001',
|
||||||
|
isDefault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'addr_002',
|
||||||
|
name: '李四',
|
||||||
|
phone: '13900139001',
|
||||||
|
province: '上海市',
|
||||||
|
city: '上海市',
|
||||||
|
district: '浦东新区',
|
||||||
|
detail: '陆家嘴环路1000号汇亚大厦20层',
|
||||||
|
isDefault: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 保存模拟地址到本地存储
|
||||||
|
uni.setStorageSync('addresses', JSON.stringify(mockAddresses))
|
||||||
|
console.log('loadAddressList: 模拟地址已保存到本地存储')
|
||||||
|
|
||||||
|
// 转换为checkout页面格式
|
||||||
|
addressList.value = mockAddresses.map((addr: any) => ({
|
||||||
|
id: addr.id,
|
||||||
|
recipient_name: addr.name,
|
||||||
|
phone: addr.phone,
|
||||||
|
province: addr.province,
|
||||||
|
city: addr.city,
|
||||||
|
district: addr.district,
|
||||||
|
detail: addr.detail,
|
||||||
|
is_default: addr.isDefault
|
||||||
|
}))
|
||||||
|
console.log('loadAddressList: 模拟地址已加载到地址列表,数量:', addressList.value.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('loadAddressList: 最终地址列表:', addressList.value)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('从Supabase加载地址列表失败:', error)
|
||||||
|
// 失败时从本地存储加载
|
||||||
const storedAddresses = uni.getStorageSync('addresses')
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
if (storedAddresses) {
|
if (storedAddresses) {
|
||||||
try {
|
try {
|
||||||
@@ -621,13 +992,14 @@ const loadAddressList = async () => {
|
|||||||
addressList.value = []
|
addressList.value = []
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('解析地址数据失败:', err)
|
console.error('解析本地地址数据失败:', err)
|
||||||
addressList.value = []
|
addressList.value = []
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addressList.value = []
|
addressList.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 选择地址
|
// 选择地址
|
||||||
const handleSelectAddress = (address: any) => {
|
const handleSelectAddress = (address: any) => {
|
||||||
@@ -1001,6 +1373,7 @@ const clearShoppingCart = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 返回
|
// 返回
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
|
|||||||
1392
pages/mall/consumer/product-detail copy 2.uvue
Normal file
1392
pages/mall/consumer/product-detail copy 2.uvue
Normal file
File diff suppressed because it is too large
Load Diff
1163
pages/mall/consumer/product-detail copy.uvue
Normal file
1163
pages/mall/consumer/product-detail copy.uvue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -109,6 +109,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ProductType, MerchantType, ProductSkuType } from '@/types/mall-types.uts'
|
import { ProductType, MerchantType, ProductSkuType } from '@/types/mall-types.uts'
|
||||||
|
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -238,49 +239,154 @@ export default {
|
|||||||
uni.setStorageSync('footprints', JSON.stringify(footprints))
|
uni.setStorageSync('footprints', JSON.stringify(footprints))
|
||||||
},
|
},
|
||||||
|
|
||||||
loadProductDetail(productId: string, options: any = {}) {
|
async loadProductDetail(productId: string, options: any = {}) {
|
||||||
// 根据商品ID生成一个基础价格(如果没有传入价格)
|
// 尝试从数据库加载
|
||||||
|
let dbProductRaw = null
|
||||||
|
try {
|
||||||
|
console.log('正在尝试从数据库加载商品详情:', productId)
|
||||||
|
dbProductRaw = await supabaseService.getProductById(productId)
|
||||||
|
console.log('数据库返回的商品详情 (原始数据):', dbProductRaw)
|
||||||
|
|
||||||
|
// 调试:打印数据库返回的所有字段
|
||||||
|
if (dbProductRaw) {
|
||||||
|
console.log('数据库返回字段详情:')
|
||||||
|
if (Array.isArray(dbProductRaw)) {
|
||||||
|
console.log('返回数据是数组,长度:', dbProductRaw.length)
|
||||||
|
if (dbProductRaw.length > 0) {
|
||||||
|
const firstItem = dbProductRaw[0]
|
||||||
|
console.log('数组第一个元素:', firstItem)
|
||||||
|
for (const key in firstItem) {
|
||||||
|
console.log(` ${key}:`, firstItem[key], typeof firstItem[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('返回数据是对象')
|
||||||
|
for (const key in dbProductRaw) {
|
||||||
|
console.log(` ${key}:`, dbProductRaw[key], typeof dbProductRaw[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load product from DB', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理数据库返回数据:可能是数组或对象
|
||||||
|
let dbProduct = null
|
||||||
|
if (dbProductRaw) {
|
||||||
|
if (Array.isArray(dbProductRaw)) {
|
||||||
|
if (dbProductRaw.length > 0) {
|
||||||
|
dbProduct = dbProductRaw[0] // 取数组第一个元素
|
||||||
|
} else {
|
||||||
|
console.warn('数据库返回空数组')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dbProduct = dbProductRaw // 已经是对象
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbProduct) {
|
||||||
|
console.log('使用数据库数据渲染页面')
|
||||||
|
|
||||||
|
// 调试:打印dbProduct的详细结构和类型
|
||||||
|
console.log('dbProduct类型:', typeof dbProduct)
|
||||||
|
console.log('dbProduct原型:', Object.getPrototypeOf(dbProduct))
|
||||||
|
console.log('dbProduct的键:')
|
||||||
|
for (let key in dbProduct) {
|
||||||
|
console.log(' ', key, ':', dbProduct[key], '类型:', typeof dbProduct[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证必要字段,如果关键字段缺失则使用模拟数据
|
||||||
|
// 注意:数据库返回的字段可能与本地ProductType不完全匹配
|
||||||
|
console.log('验证必要字段,dbProduct:', dbProduct)
|
||||||
|
|
||||||
|
// 尝试多种方式访问属性
|
||||||
|
const idValue = dbProduct.id !== undefined ? dbProduct.id : (dbProduct['id'] !== undefined ? dbProduct['id'] : undefined)
|
||||||
|
const nameValue = dbProduct.name !== undefined ? dbProduct.name : (dbProduct['name'] !== undefined ? dbProduct['name'] : undefined)
|
||||||
|
const priceValue = dbProduct.price !== undefined ? dbProduct.price : (dbProduct['price'] !== undefined ? dbProduct['price'] : undefined)
|
||||||
|
|
||||||
|
const hasId = idValue !== undefined && idValue !== null
|
||||||
|
const hasName = nameValue !== undefined && nameValue !== null
|
||||||
|
const hasPrice = priceValue !== undefined && priceValue !== null
|
||||||
|
|
||||||
|
const hasRequiredFields = dbProduct && hasId && hasName && hasPrice
|
||||||
|
console.log('字段检查 - id:', idValue, 'hasId:', hasId, 'name:', nameValue, 'hasName:', hasName, 'price:', priceValue, 'hasPrice:', hasPrice)
|
||||||
|
console.log('hasRequiredFields:', hasRequiredFields)
|
||||||
|
|
||||||
|
if (!hasRequiredFields) {
|
||||||
|
console.warn('数据库返回数据缺少必要字段,使用模拟数据')
|
||||||
|
// 继续执行,会进入下面的else分支
|
||||||
|
dbProduct = null
|
||||||
|
} else {
|
||||||
|
// 更新dbProduct的字段为实际值,确保后续使用正确的属性访问
|
||||||
|
if (dbProduct.id === undefined && idValue !== undefined) dbProduct.id = idValue
|
||||||
|
if (dbProduct.name === undefined && nameValue !== undefined) dbProduct.name = nameValue
|
||||||
|
if (dbProduct.price === undefined && priceValue !== undefined) dbProduct.price = priceValue
|
||||||
|
// 使用数据库数据 - 处理字段映射
|
||||||
|
// 数据库Product接口和本地ProductType接口字段可能不同
|
||||||
|
const images = [] as Array<string>
|
||||||
|
|
||||||
|
// 处理图片字段:数据库可能是image,本地需要images数组
|
||||||
|
if (dbProduct.image) {
|
||||||
|
images.push(dbProduct.image)
|
||||||
|
} else if (options.image) {
|
||||||
|
images.push(decodeURIComponent(options.image as string))
|
||||||
|
} else {
|
||||||
|
images.push('/static/product1.jpg')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 补充模拟图片
|
||||||
|
images.push('/static/product2.jpg')
|
||||||
|
images.push('/static/product3.jpg')
|
||||||
|
|
||||||
|
// 映射字段:数据库shop_id对应本地merchant_id
|
||||||
|
const merchantId = dbProduct.shop_id || dbProduct.merchant_id || 'merchant_001'
|
||||||
|
|
||||||
|
// 确保数值字段有效
|
||||||
|
const price = typeof dbProduct.price === 'number' ? dbProduct.price : 0
|
||||||
|
const stock = (dbProduct.stock != null && !isNaN(Number(dbProduct.stock))) ? Math.floor(Number(dbProduct.stock)) : 100
|
||||||
|
const sales = (dbProduct.sales != null && !isNaN(Number(dbProduct.sales))) ? Math.floor(Number(dbProduct.sales)) : 50
|
||||||
|
|
||||||
|
this.product = {
|
||||||
|
id: dbProduct.id || productId,
|
||||||
|
merchant_id: merchantId,
|
||||||
|
category_id: dbProduct.category_id || 'cat_001',
|
||||||
|
name: dbProduct.name || '商品名称',
|
||||||
|
description: dbProduct.description || '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
|
||||||
|
images: images,
|
||||||
|
price: price,
|
||||||
|
original_price: (dbProduct.original_price != null && !isNaN(Number(dbProduct.original_price))) ? Number(dbProduct.original_price) : null,
|
||||||
|
stock: stock,
|
||||||
|
sales: sales,
|
||||||
|
status: 1,
|
||||||
|
created_at: dbProduct.created_at || '2024-01-01'
|
||||||
|
} as ProductType
|
||||||
|
console.log('页面 product 对象已更新:', this.product)
|
||||||
|
console.log('商品价格:', this.product.price, '库存:', this.product.stock, '销量:', this.product.sales)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('数据库无数据或加载失败,使用模拟数据')
|
||||||
|
// 数据库无数据时,使用原有模拟逻辑
|
||||||
const generatePriceFromId = (id: string): number => {
|
const generatePriceFromId = (id: string): number => {
|
||||||
// 简单哈希函数,将字符串转换为一个在50-500之间的价格
|
|
||||||
let hash = 0
|
let hash = 0
|
||||||
for (let i = 0; i < id.length; i++) {
|
for (let i = 0; i < id.length; i++) {
|
||||||
hash = (hash << 5) - hash + id.charCodeAt(i)
|
hash = (hash << 5) - hash + id.charCodeAt(i)
|
||||||
hash |= 0 // 转换为32位整数
|
hash |= 0
|
||||||
}
|
}
|
||||||
// 将哈希值映射到50-500之间
|
|
||||||
const price = 50 + Math.abs(hash % 450)
|
const price = 50 + Math.abs(hash % 450)
|
||||||
// 保留两位小数
|
|
||||||
return parseFloat(price.toFixed(2))
|
return parseFloat(price.toFixed(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先使用传入的参数,否则根据商品ID生成价格
|
|
||||||
const basePrice = options.price ? parseFloat(options.price) : generatePriceFromId(productId)
|
const basePrice = options.price ? parseFloat(options.price) : generatePriceFromId(productId)
|
||||||
// 原价比现价高20%左右
|
|
||||||
const originalPrice = options.originalPrice ? parseFloat(options.originalPrice) : parseFloat((basePrice * 1.2).toFixed(2))
|
const originalPrice = options.originalPrice ? parseFloat(options.originalPrice) : parseFloat((basePrice * 1.2).toFixed(2))
|
||||||
|
|
||||||
// 优先使用传入的商品名称,否则根据商品ID生成名称
|
|
||||||
const productName = options.name ? decodeURIComponent(options.name) : (() => {
|
const productName = options.name ? decodeURIComponent(options.name) : (() => {
|
||||||
// 如果options.name未传入,使用默认的商品名称生成逻辑
|
const productNames = ['高品质运动休闲鞋', '时尚简约双肩背包', '多功能智能手环', '便携式蓝牙音箱', '全自动雨伞', '抗菌防螨床上四件套', '不锈钢保温杯', '无线充电器', '高清行车记录仪', '智能体脂秤']
|
||||||
const productNames = [
|
|
||||||
'高品质运动休闲鞋',
|
|
||||||
'时尚简约双肩背包',
|
|
||||||
'多功能智能手环',
|
|
||||||
'便携式蓝牙音箱',
|
|
||||||
'全自动雨伞',
|
|
||||||
'抗菌防螨床上四件套',
|
|
||||||
'不锈钢保温杯',
|
|
||||||
'无线充电器',
|
|
||||||
'高清行车记录仪',
|
|
||||||
'智能体脂秤'
|
|
||||||
]
|
|
||||||
const nameIndex = Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % productNames.length
|
const nameIndex = Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % productNames.length
|
||||||
return productNames[nameIndex]
|
return productNames[nameIndex]
|
||||||
})()
|
})()
|
||||||
|
|
||||||
// 优先使用传入的图片,否则使用默认图片
|
|
||||||
const productImage = options.image ? decodeURIComponent(options.image) : '/static/product1.jpg'
|
const productImage = options.image ? decodeURIComponent(options.image) : '/static/product1.jpg'
|
||||||
|
|
||||||
// 模拟销量和库存,使其更真实
|
|
||||||
const sales = 1000 + Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5000
|
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
|
const stock = 50 + Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 200
|
||||||
|
|
||||||
@@ -290,11 +396,7 @@ export default {
|
|||||||
category_id: 'cat_' + (Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 10 + 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,
|
name: productName,
|
||||||
description: '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
|
description: '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
|
||||||
images: [
|
images: [productImage, '/static/product2.jpg', '/static/product3.jpg'],
|
||||||
productImage,
|
|
||||||
'/static/product2.jpg',
|
|
||||||
'/static/product3.jpg'
|
|
||||||
],
|
|
||||||
price: basePrice,
|
price: basePrice,
|
||||||
original_price: originalPrice,
|
original_price: originalPrice,
|
||||||
stock: stock,
|
stock: stock,
|
||||||
@@ -302,6 +404,7 @@ export default {
|
|||||||
status: 1,
|
status: 1,
|
||||||
created_at: '2024-01-15'
|
created_at: '2024-01-15'
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 根据商家ID生成不同的商家信息
|
// 根据商家ID生成不同的商家信息
|
||||||
const merchantIndex = Math.abs(this.product.merchant_id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5
|
const merchantIndex = Math.abs(this.product.merchant_id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5
|
||||||
1359
pages/mall/consumer/product-detail copy完成图片数量数据获取.uvue
Normal file
1359
pages/mall/consumer/product-detail copy完成图片数量数据获取.uvue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
<view class="product-detail-page">
|
<view class="product-detail-page">
|
||||||
<!-- 商品图片轮播 -->
|
<!-- 商品图片轮播 -->
|
||||||
<view class="product-images">
|
<view class="product-images">
|
||||||
<swiper class="image-swiper" :indicator-dots="true" :autoplay="false">
|
<swiper class="image-swiper" :indicator-dots="true" :autoplay="false" @change="onSwiperChange">
|
||||||
<swiper-item v-for="(image, index) in product.images" :key="index">
|
<swiper-item v-for="(image, index) in product.images" :key="index">
|
||||||
<image :src="image" class="product-image" mode="aspectFit" />
|
<image :src="image" class="product-image" mode="aspectFit" />
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
@@ -34,6 +34,23 @@
|
|||||||
<text class="enter-shop" @click.stop="goToShop">进店 ></text>
|
<text class="enter-shop" @click.stop="goToShop">进店 ></text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 功能主治(药品功能) -->
|
||||||
|
<view class="function-section" v-if="product.usage">
|
||||||
|
<text class="function-title">功能主治</text>
|
||||||
|
<text class="function-content">{{ product.usage }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品参数 -->
|
||||||
|
<view class="params-section" @click="showParamsModal">
|
||||||
|
<text class="params-title">商品参数</text>
|
||||||
|
<view class="params-summary">
|
||||||
|
<text class="params-item" v-if="product.specification">规格: {{ product.specification }}</text>
|
||||||
|
<text class="params-item" v-if="product.expiry_date">有效期: {{ product.expiry_date }}</text>
|
||||||
|
<text class="params-item" v-if="product.approval_number">批准文号: {{ product.approval_number }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="params-arrow">></text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 规格选择 -->
|
<!-- 规格选择 -->
|
||||||
<view class="spec-section" @click="showSpecModal">
|
<view class="spec-section" @click="showSpecModal">
|
||||||
<text class="spec-title">规格</text>
|
<text class="spec-title">规格</text>
|
||||||
@@ -65,6 +82,15 @@
|
|||||||
<view class="product-description">
|
<view class="product-description">
|
||||||
<view class="section-title">商品详情</view>
|
<view class="section-title">商品详情</view>
|
||||||
<text class="description-text">{{ product.description || '暂无详细描述' }}</text>
|
<text class="description-text">{{ product.description || '暂无详细描述' }}</text>
|
||||||
|
<!-- 商品详情图片 -->
|
||||||
|
<view class="detail-images" v-if="product.images && product.images.length > 0">
|
||||||
|
<image v-for="(img, index) in product.images"
|
||||||
|
:key="index"
|
||||||
|
:src="img"
|
||||||
|
class="detail-image"
|
||||||
|
mode="widthFix"
|
||||||
|
@click="previewImage(index)" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 底部操作栏 -->
|
<!-- 底部操作栏 -->
|
||||||
@@ -104,6 +130,50 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品参数弹窗 -->
|
||||||
|
<view v-if="showParams" class="params-modal" @click="hideParamsModal">
|
||||||
|
<view class="params-content" @click.stop>
|
||||||
|
<view class="params-header">
|
||||||
|
<text class="params-title">商品参数</text>
|
||||||
|
<text class="close-btn" @click="hideParamsModal">×</text>
|
||||||
|
</view>
|
||||||
|
<view class="params-list">
|
||||||
|
<view class="params-item" v-if="product.specification">
|
||||||
|
<text class="params-label">规格</text>
|
||||||
|
<text class="params-value">{{ product.specification }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="params-item" v-if="product.usage">
|
||||||
|
<text class="params-label">功能主治</text>
|
||||||
|
<text class="params-value">{{ product.usage }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="params-item" v-if="product.side_effects">
|
||||||
|
<text class="params-label">副作用</text>
|
||||||
|
<text class="params-value">{{ product.side_effects }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="params-item" v-if="product.precautions">
|
||||||
|
<text class="params-label">注意事项</text>
|
||||||
|
<text class="params-value">{{ product.precautions }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="params-item" v-if="product.expiry_date">
|
||||||
|
<text class="params-label">有效期</text>
|
||||||
|
<text class="params-value">{{ product.expiry_date }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="params-item" v-if="product.storage_conditions">
|
||||||
|
<text class="params-label">储存条件</text>
|
||||||
|
<text class="params-value">{{ product.storage_conditions }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="params-item" v-if="product.approval_number">
|
||||||
|
<text class="params-label">批准文号</text>
|
||||||
|
<text class="params-value">{{ product.approval_number }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="params-item" v-if="product.tags && product.tags.length > 0">
|
||||||
|
<text class="params-label">标签</text>
|
||||||
|
<text class="params-value">{{ product.tags.join(', ') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -148,7 +218,8 @@ export default {
|
|||||||
selectedSkuId: '',
|
selectedSkuId: '',
|
||||||
selectedSpec: '',
|
selectedSpec: '',
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
isFavorite: false
|
isFavorite: false,
|
||||||
|
showParams: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(options: any) {
|
onLoad(options: any) {
|
||||||
@@ -241,42 +312,186 @@ export default {
|
|||||||
|
|
||||||
async loadProductDetail(productId: string, options: any = {}) {
|
async loadProductDetail(productId: string, options: any = {}) {
|
||||||
// 尝试从数据库加载
|
// 尝试从数据库加载
|
||||||
let dbProduct = null
|
let dbProductRaw = null
|
||||||
try {
|
try {
|
||||||
console.log('正在尝试从数据库加载商品详情:', productId)
|
console.log('正在尝试从数据库加载商品详情:', productId)
|
||||||
dbProduct = await supabaseService.getProductById(productId)
|
dbProductRaw = await supabaseService.getProductById(productId)
|
||||||
console.log('数据库返回的商品详情:', dbProduct)
|
console.log('数据库返回的商品详情 (原始数据):', dbProductRaw)
|
||||||
|
|
||||||
|
// 调试:打印数据库返回的所有字段
|
||||||
|
if (dbProductRaw) {
|
||||||
|
console.log('数据库返回字段详情:')
|
||||||
|
if (Array.isArray(dbProductRaw)) {
|
||||||
|
console.log('返回数据是数组,长度:', dbProductRaw.length)
|
||||||
|
if (dbProductRaw.length > 0) {
|
||||||
|
const firstItem = dbProductRaw[0]
|
||||||
|
console.log('数组第一个元素:', firstItem)
|
||||||
|
for (const key in firstItem) {
|
||||||
|
console.log(` ${key}:`, firstItem[key], typeof firstItem[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('返回数据是对象')
|
||||||
|
for (const key in dbProductRaw) {
|
||||||
|
console.log(` ${key}:`, dbProductRaw[key], typeof dbProductRaw[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load product from DB', e)
|
console.error('Failed to load product from DB', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理数据库返回数据:可能是数组或对象
|
||||||
|
let dbProduct = null
|
||||||
|
if (dbProductRaw) {
|
||||||
|
if (Array.isArray(dbProductRaw)) {
|
||||||
|
if (dbProductRaw.length > 0) {
|
||||||
|
dbProduct = dbProductRaw[0] // 取数组第一个元素
|
||||||
|
} else {
|
||||||
|
console.warn('数据库返回空数组')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dbProduct = dbProductRaw // 已经是对象
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (dbProduct) {
|
if (dbProduct) {
|
||||||
console.log('使用数据库数据渲染页面')
|
console.log('使用数据库数据渲染页面')
|
||||||
// 使用数据库数据
|
|
||||||
const images = [] as Array<string>
|
|
||||||
if (dbProduct.image) images.push(dbProduct.image)
|
|
||||||
else if (options.image) images.push(decodeURIComponent(options.image as string))
|
|
||||||
else images.push('/static/product1.jpg')
|
|
||||||
|
|
||||||
// 补充模拟图片
|
// 调试:打印dbProduct的详细结构和类型
|
||||||
images.push('/static/product2.jpg')
|
console.log('dbProduct类型:', typeof dbProduct)
|
||||||
images.push('/static/product3.jpg')
|
console.log('dbProduct原型:', Object.getPrototypeOf(dbProduct))
|
||||||
|
console.log('dbProduct的键:')
|
||||||
|
for (let key in dbProduct) {
|
||||||
|
console.log(' ', key, ':', dbProduct[key], '类型:', typeof dbProduct[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证必要字段,如果关键字段缺失则使用模拟数据
|
||||||
|
// 注意:数据库返回的字段可能与本地ProductType不完全匹配
|
||||||
|
console.log('验证必要字段,dbProduct:', dbProduct)
|
||||||
|
|
||||||
|
// 尝试多种方式访问属性
|
||||||
|
const idValue = dbProduct.id !== undefined ? dbProduct.id : (dbProduct['id'] !== undefined ? dbProduct['id'] : undefined)
|
||||||
|
const nameValue = dbProduct.name !== undefined ? dbProduct.name : (dbProduct['name'] !== undefined ? dbProduct['name'] : undefined)
|
||||||
|
const priceValue = dbProduct.price !== undefined ? dbProduct.price : (dbProduct['price'] !== undefined ? dbProduct['price'] : undefined)
|
||||||
|
|
||||||
|
const hasId = idValue !== undefined && idValue !== null
|
||||||
|
const hasName = nameValue !== undefined && nameValue !== null
|
||||||
|
const hasPrice = priceValue !== undefined && priceValue !== null
|
||||||
|
|
||||||
|
const hasRequiredFields = dbProduct && hasId && hasName && hasPrice
|
||||||
|
console.log('字段检查 - id:', idValue, 'hasId:', hasId, 'name:', nameValue, 'hasName:', hasName, 'price:', priceValue, 'hasPrice:', hasPrice)
|
||||||
|
console.log('hasRequiredFields:', hasRequiredFields)
|
||||||
|
|
||||||
|
if (!hasRequiredFields) {
|
||||||
|
console.warn('数据库返回数据缺少必要字段,使用模拟数据')
|
||||||
|
// 继续执行,会进入下面的else分支
|
||||||
|
dbProduct = null
|
||||||
|
} else {
|
||||||
|
// 更新dbProduct的字段为实际值,确保后续使用正确的属性访问
|
||||||
|
if (dbProduct.id === undefined && idValue !== undefined) dbProduct.id = idValue
|
||||||
|
if (dbProduct.name === undefined && nameValue !== undefined) dbProduct.name = nameValue
|
||||||
|
if (dbProduct.price === undefined && priceValue !== undefined) dbProduct.price = priceValue
|
||||||
|
// 使用数据库数据 - 处理字段映射
|
||||||
|
// 数据库Product接口和本地ProductType接口字段可能不同
|
||||||
|
const images = [] as Array<string>
|
||||||
|
|
||||||
|
// 处理图片字段:优先使用images字段,其次使用image字段
|
||||||
|
console.log('处理数据库图片字段:')
|
||||||
|
console.log('dbProduct.images:', dbProduct.images, '类型:', typeof dbProduct.images)
|
||||||
|
console.log('dbProduct.image:', dbProduct.image, '类型:', typeof dbProduct.image)
|
||||||
|
|
||||||
|
// 尝试从数据库的images字段获取图片(可能是字符串或数组)
|
||||||
|
if (dbProduct.images) {
|
||||||
|
let imagesArray: any[] = []
|
||||||
|
if (typeof dbProduct.images === 'string') {
|
||||||
|
try {
|
||||||
|
imagesArray = JSON.parse(dbProduct.images)
|
||||||
|
console.log('解析images字符串成功:', imagesArray)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析images字段失败:', e, dbProduct.images)
|
||||||
|
// 如果不是JSON,尝试按逗号分割
|
||||||
|
if (dbProduct.images.includes(',')) {
|
||||||
|
imagesArray = dbProduct.images.split(',').map((img: string) => img.trim())
|
||||||
|
} else if (dbProduct.images) {
|
||||||
|
imagesArray = [dbProduct.images]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(dbProduct.images)) {
|
||||||
|
imagesArray = dbProduct.images
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imagesArray.length > 0) {
|
||||||
|
console.log('从数据库images字段获取图片数组:', imagesArray)
|
||||||
|
for (const img of imagesArray) {
|
||||||
|
if (typeof img === 'string' && img) {
|
||||||
|
images.push(img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有从images字段获取到图片,尝试使用image字段
|
||||||
|
if (images.length === 0 && dbProduct.image) {
|
||||||
|
console.log('使用单张图片字段:', dbProduct.image)
|
||||||
|
images.push(dbProduct.image)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果仍然没有图片,使用传入的图片或默认图片
|
||||||
|
if (images.length === 0) {
|
||||||
|
if (options.image) {
|
||||||
|
images.push(decodeURIComponent(options.image as string))
|
||||||
|
} else {
|
||||||
|
images.push('/static/product1.jpg')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 补充模拟图片(如果图片数量不足3张)
|
||||||
|
const needSupplementCount = 3 - images.length
|
||||||
|
if (needSupplementCount > 0) {
|
||||||
|
const supplementalImages = ['/static/product2.jpg', '/static/product3.jpg']
|
||||||
|
for (let i = 0; i < needSupplementCount && i < supplementalImages.length; i++) {
|
||||||
|
images.push(supplementalImages[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('最终图片数组:', images)
|
||||||
|
|
||||||
|
// 映射字段:数据库shop_id对应本地merchant_id
|
||||||
|
const merchantId = dbProduct.shop_id || dbProduct.merchant_id || 'merchant_001'
|
||||||
|
|
||||||
|
// 确保数值字段有效
|
||||||
|
const price = typeof dbProduct.price === 'number' ? dbProduct.price : 0
|
||||||
|
const stock = (dbProduct.stock != null && !isNaN(Number(dbProduct.stock))) ? Math.floor(Number(dbProduct.stock)) : 100
|
||||||
|
const sales = (dbProduct.sales != null && !isNaN(Number(dbProduct.sales))) ? Math.floor(Number(dbProduct.sales)) : 50
|
||||||
|
|
||||||
this.product = {
|
this.product = {
|
||||||
id: dbProduct.id,
|
id: dbProduct.id || productId,
|
||||||
merchant_id: dbProduct.shop_id || 'merchant_001',
|
merchant_id: merchantId,
|
||||||
category_id: dbProduct.category_id,
|
category_id: dbProduct.category_id || 'cat_001',
|
||||||
name: dbProduct.name,
|
name: dbProduct.name || '商品名称',
|
||||||
description: dbProduct.description || '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
|
description: dbProduct.description || '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
|
||||||
images: images,
|
images: images,
|
||||||
price: dbProduct.price,
|
price: price,
|
||||||
original_price: dbProduct.original_price || null,
|
original_price: (dbProduct.original_price != null && !isNaN(Number(dbProduct.original_price))) ? Number(dbProduct.original_price) : null,
|
||||||
stock: dbProduct.stock !== undefined ? dbProduct.stock : 0, // 确保使用数据库中的库存
|
stock: stock,
|
||||||
sales: dbProduct.sales !== undefined ? dbProduct.sales : 0, // 确保使用数据库中的销量
|
sales: sales,
|
||||||
status: 1,
|
status: 1,
|
||||||
created_at: dbProduct.created_at || '2024-01-01'
|
created_at: dbProduct.created_at || '2024-01-01',
|
||||||
|
// 药品相关字段
|
||||||
|
specification: dbProduct.specification || null,
|
||||||
|
usage: dbProduct.usage || null,
|
||||||
|
side_effects: dbProduct.side_effects || null,
|
||||||
|
precautions: dbProduct.precautions || null,
|
||||||
|
expiry_date: dbProduct.expiry_date || null,
|
||||||
|
storage_conditions: dbProduct.storage_conditions || null,
|
||||||
|
approval_number: dbProduct.approval_number || null,
|
||||||
|
tags: dbProduct.tags ? (typeof dbProduct.tags === 'string' ? JSON.parse(dbProduct.tags) : dbProduct.tags) : []
|
||||||
} as ProductType
|
} as ProductType
|
||||||
console.log('页面 product 对象已更新:', this.product)
|
console.log('页面 product 对象已更新:', this.product)
|
||||||
|
console.log('商品图片数组:', this.product.images)
|
||||||
|
console.log('商品价格:', this.product.price, '库存:', this.product.stock, '销量:', this.product.sales)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('数据库无数据或加载失败,使用模拟数据')
|
console.log('数据库无数据或加载失败,使用模拟数据')
|
||||||
// 数据库无数据时,使用原有模拟逻辑
|
// 数据库无数据时,使用原有模拟逻辑
|
||||||
@@ -379,6 +594,10 @@ export default {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onSwiperChange(e: any) {
|
||||||
|
this.currentImageIndex = e.detail.current
|
||||||
|
},
|
||||||
|
|
||||||
showSpecModal() {
|
showSpecModal() {
|
||||||
this.showSpec = true
|
this.showSpec = true
|
||||||
},
|
},
|
||||||
@@ -626,6 +845,21 @@ export default {
|
|||||||
|
|
||||||
getAvailableStock() {
|
getAvailableStock() {
|
||||||
return this.getMaxQuantity()
|
return this.getMaxQuantity()
|
||||||
|
},
|
||||||
|
|
||||||
|
previewImage(index: number) {
|
||||||
|
uni.previewImage({
|
||||||
|
current: index,
|
||||||
|
urls: this.product.images
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
showParamsModal() {
|
||||||
|
this.showParams = true
|
||||||
|
},
|
||||||
|
|
||||||
|
hideParamsModal() {
|
||||||
|
this.showParams = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -993,4 +1227,166 @@ export default {
|
|||||||
width: 100rpx;
|
width: 100rpx;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 功能主治样式 */
|
||||||
|
.function-section {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 15rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-content {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品参数样式 */
|
||||||
|
.params-section {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
width: 120rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-summary {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-item {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
margin-bottom: 5rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-arrow {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品参数弹窗样式 */
|
||||||
|
.params-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-content {
|
||||||
|
background-color: #fff;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 80vh;
|
||||||
|
border-radius: 20rpx 20rpx 0 0;
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-list {
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 150rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-value {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品详情图片样式 */
|
||||||
|
.detail-images {
|
||||||
|
margin-top: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-image {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 电脑端适配 */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.params-section {
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-summary {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-item {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 0;
|
||||||
|
text-align: center;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 0 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-arrow {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -85,6 +85,15 @@ export type ProductType = {
|
|||||||
sales: number
|
sales: number
|
||||||
status: number
|
status: number
|
||||||
created_at: string
|
created_at: string
|
||||||
|
// 药品相关字段
|
||||||
|
specification?: string | null // 规格说明
|
||||||
|
usage?: string | null // 用法用量
|
||||||
|
side_effects?: string | null // 副作用
|
||||||
|
precautions?: string | null // 注意事项
|
||||||
|
expiry_date?: string | null // 有效期
|
||||||
|
storage_conditions?: string | null // 储存条件
|
||||||
|
approval_number?: string | null // 批准文号
|
||||||
|
tags?: Array<string> | null // 商品标签
|
||||||
}
|
}
|
||||||
|
|
||||||
// 商品SKU类型
|
// 商品SKU类型
|
||||||
|
|||||||
@@ -25,14 +25,46 @@ export interface Product {
|
|||||||
original_price?: number
|
original_price?: number
|
||||||
image?: string
|
image?: string
|
||||||
manufacturer: string
|
manufacturer: string
|
||||||
sales: number
|
sales?: number
|
||||||
stock: number
|
stock?: number
|
||||||
badge?: string
|
badge?: string
|
||||||
shop_id?: string
|
shop_id?: string
|
||||||
shop_name?: string
|
shop_name?: string
|
||||||
created_at?: string
|
created_at?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CartItem {
|
||||||
|
id: string
|
||||||
|
user_id: string
|
||||||
|
product_id: string
|
||||||
|
sku_id?: string
|
||||||
|
quantity: number
|
||||||
|
selected: boolean
|
||||||
|
product_name?: string
|
||||||
|
product_image?: string
|
||||||
|
product_price?: number
|
||||||
|
product_specification?: string
|
||||||
|
shop_id?: string
|
||||||
|
shop_name?: string
|
||||||
|
created_at?: string
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserAddress {
|
||||||
|
id: string
|
||||||
|
user_id: string
|
||||||
|
recipient_name: string
|
||||||
|
phone: string
|
||||||
|
province: string
|
||||||
|
city: string
|
||||||
|
district: string
|
||||||
|
detail_address: string
|
||||||
|
postal_code?: string
|
||||||
|
is_default: boolean
|
||||||
|
created_at?: string
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface PaginatedResponse<T> {
|
export interface PaginatedResponse<T> {
|
||||||
data: T[]
|
data: T[]
|
||||||
total: number
|
total: number
|
||||||
@@ -42,6 +74,17 @@ export interface PaginatedResponse<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SupabaseService {
|
class SupabaseService {
|
||||||
|
// 获取当前用户ID
|
||||||
|
private getCurrentUserId(): string | null {
|
||||||
|
try {
|
||||||
|
const userId = uni.getStorageSync('user_id')
|
||||||
|
return userId ? userId as string : null
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取用户ID失败:', e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取所有分类
|
// 获取所有分类
|
||||||
async getCategories(): Promise<Category[]> {
|
async getCategories(): Promise<Category[]> {
|
||||||
try {
|
try {
|
||||||
@@ -176,7 +219,7 @@ class SupabaseService {
|
|||||||
.select('*')
|
.select('*')
|
||||||
.eq('id', productId)
|
.eq('id', productId)
|
||||||
.single()
|
.single()
|
||||||
.execute()
|
.executeAs<Product>()
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
console.error('获取商品详情失败:', response.error)
|
console.error('获取商品详情失败:', response.error)
|
||||||
@@ -304,6 +347,561 @@ class SupabaseService {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取当前用户的购物车商品(关联商品和店铺信息)
|
||||||
|
async getCartItems(): Promise<CartItem[]> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.warn('用户未登录,无法获取购物车')
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询购物车表,并关联商品表(使用内联关联)
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_shopping_cart')
|
||||||
|
.select(`
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
product_id,
|
||||||
|
sku_id,
|
||||||
|
quantity,
|
||||||
|
selected,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
products!inner (
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
image,
|
||||||
|
price,
|
||||||
|
specification,
|
||||||
|
shop_id,
|
||||||
|
shop_name
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('获取购物车失败:', response.error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理返回数据,构建CartItem数组
|
||||||
|
const cartItems: CartItem[] = []
|
||||||
|
if (response.data && Array.isArray(response.data)) {
|
||||||
|
for (const item of response.data) {
|
||||||
|
const product = item.products as any
|
||||||
|
|
||||||
|
cartItems.push({
|
||||||
|
id: item.id as string,
|
||||||
|
user_id: item.user_id as string,
|
||||||
|
product_id: item.product_id as string,
|
||||||
|
sku_id: item.sku_id as string,
|
||||||
|
quantity: item.quantity as number,
|
||||||
|
selected: item.selected as boolean,
|
||||||
|
product_name: product?.name as string,
|
||||||
|
product_image: product?.image as string,
|
||||||
|
product_price: product?.price as number,
|
||||||
|
product_specification: product?.specification as string,
|
||||||
|
shop_id: product?.shop_id as string,
|
||||||
|
shop_name: product?.shop_name as string,
|
||||||
|
created_at: item.created_at as string,
|
||||||
|
updated_at: item.updated_at as string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cartItems
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取购物车异常:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加商品到购物车
|
||||||
|
async addToCart(productId: string, quantity: number = 1, skuId?: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法添加商品到购物车')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查商品是否已在购物车中
|
||||||
|
const existingResponse = await supa
|
||||||
|
.from('ml_shopping_cart')
|
||||||
|
.select('*')
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.eq('product_id', productId)
|
||||||
|
.eq('sku_id', skuId || '')
|
||||||
|
.single()
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
let response
|
||||||
|
if (existingResponse.data) {
|
||||||
|
// 商品已存在,更新数量
|
||||||
|
const existingItem = existingResponse.data as any
|
||||||
|
response = await supa
|
||||||
|
.from('ml_shopping_cart')
|
||||||
|
.update({
|
||||||
|
quantity: (existingItem.quantity || 0) + quantity,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('id', existingItem.id)
|
||||||
|
.execute()
|
||||||
|
} else {
|
||||||
|
// 商品不存在,添加新记录
|
||||||
|
response = await supa
|
||||||
|
.from('ml_shopping_cart')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
product_id: productId,
|
||||||
|
sku_id: skuId || null,
|
||||||
|
quantity: quantity,
|
||||||
|
selected: true,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('添加商品到购物车失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('添加商品到购物车异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新购物车商品数量
|
||||||
|
async updateCartItemQuantity(cartItemId: string, quantity: number): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法更新购物车')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quantity < 1) {
|
||||||
|
// 数量小于1时删除商品
|
||||||
|
return await this.deleteCartItem(cartItemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_shopping_cart')
|
||||||
|
.update({
|
||||||
|
quantity: quantity,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('id', cartItemId)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('更新购物车商品数量失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新购物车商品数量异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新购物车商品选中状态
|
||||||
|
async updateCartItemSelection(cartItemId: string, selected: boolean): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法更新购物车')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_shopping_cart')
|
||||||
|
.update({
|
||||||
|
selected: selected,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('id', cartItemId)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('更新购物车商品选中状态失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新购物车商品选中状态异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量更新购物车商品选中状态
|
||||||
|
async batchUpdateCartItemSelection(cartItemIds: string[], selected: boolean): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法更新购物车')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_shopping_cart')
|
||||||
|
.update({
|
||||||
|
selected: selected,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.in('id', cartItemIds)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('批量更新购物车商品选中状态失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量更新购物车商品选中状态异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除购物车商品
|
||||||
|
async deleteCartItem(cartItemId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法删除购物车商品')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_shopping_cart')
|
||||||
|
.delete()
|
||||||
|
.eq('id', cartItemId)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('删除购物车商品失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除购物车商品异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除购物车商品
|
||||||
|
async batchDeleteCartItems(cartItemIds: string[]): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法删除购物车商品')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_shopping_cart')
|
||||||
|
.delete()
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.in('id', cartItemIds)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('批量删除购物车商品失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量删除购物车商品异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空购物车
|
||||||
|
async clearCart(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法清空购物车')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_shopping_cart')
|
||||||
|
.delete()
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('清空购物车失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清空购物车异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户的所有地址
|
||||||
|
async getAddresses(): Promise<UserAddress[]> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.warn('用户未登录,无法获取地址')
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_user_addresses')
|
||||||
|
.select('*')
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.order('is_default', { ascending: false })
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('获取地址失败:', response.error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data as UserAddress[]
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取地址异常:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID获取地址详情
|
||||||
|
async getAddressById(addressId: string): Promise<UserAddress | null> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.warn('用户未登录,无法获取地址')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_user_addresses')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', addressId)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.single()
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('获取地址详情失败:', response.error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data as UserAddress
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取地址详情异常:', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新地址
|
||||||
|
async addAddress(address: {
|
||||||
|
recipient_name: string
|
||||||
|
phone: string
|
||||||
|
province: string
|
||||||
|
city: string
|
||||||
|
district: string
|
||||||
|
detail_address: string
|
||||||
|
postal_code?: string
|
||||||
|
is_default?: boolean
|
||||||
|
}): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法添加地址')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果设置为默认地址,需要先取消其他默认地址
|
||||||
|
if (address.is_default) {
|
||||||
|
await this.clearDefaultAddress(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_user_addresses')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
recipient_name: address.recipient_name,
|
||||||
|
phone: address.phone,
|
||||||
|
province: address.province,
|
||||||
|
city: address.city,
|
||||||
|
district: address.district,
|
||||||
|
detail_address: address.detail_address,
|
||||||
|
postal_code: address.postal_code || null,
|
||||||
|
is_default: address.is_default || false,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('添加地址失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('添加地址异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新地址
|
||||||
|
async updateAddress(addressId: string, address: {
|
||||||
|
recipient_name?: string
|
||||||
|
phone?: string
|
||||||
|
province?: string
|
||||||
|
city?: string
|
||||||
|
district?: string
|
||||||
|
detail_address?: string
|
||||||
|
postal_code?: string
|
||||||
|
is_default?: boolean
|
||||||
|
}): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法更新地址')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果设置为默认地址,需要先取消其他默认地址
|
||||||
|
if (address.is_default) {
|
||||||
|
await this.clearDefaultAddress(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_user_addresses')
|
||||||
|
.update({
|
||||||
|
...address,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('id', addressId)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('更新地址失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新地址异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除地址
|
||||||
|
async deleteAddress(addressId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法删除地址')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_user_addresses')
|
||||||
|
.delete()
|
||||||
|
.eq('id', addressId)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('删除地址失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除地址异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认地址
|
||||||
|
async setDefaultAddress(addressId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const userId = this.getCurrentUserId()
|
||||||
|
if (!userId) {
|
||||||
|
console.error('用户未登录,无法设置默认地址')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先取消所有默认地址
|
||||||
|
await this.clearDefaultAddress(userId)
|
||||||
|
|
||||||
|
// 设置新的默认地址
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_user_addresses')
|
||||||
|
.update({
|
||||||
|
is_default: true,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('id', addressId)
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('设置默认地址失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('设置默认地址异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除用户的默认地址(内部方法)
|
||||||
|
private async clearDefaultAddress(userId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const response = await supa
|
||||||
|
.from('ml_user_addresses')
|
||||||
|
.update({
|
||||||
|
is_default: false,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.eq('is_default', true)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('清除默认地址失败:', response.error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清除默认地址异常:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出单例实例
|
// 导出单例实例
|
||||||
|
|||||||
Reference in New Issue
Block a user