|
@@ -1,13 +1,13 @@
|
|
|
import { View, ScrollView, Text } from '@tarojs/components'
|
|
import { View, ScrollView, Text } from '@tarojs/components'
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
|
-import { useState } from 'react'
|
|
|
|
|
|
|
+import { useState, useRef } from 'react'
|
|
|
import Taro from '@tarojs/taro'
|
|
import Taro from '@tarojs/taro'
|
|
|
import { deliveryAddressClient } from '@/api'
|
|
import { deliveryAddressClient } from '@/api'
|
|
|
import { InferResponseType, InferRequestType } from 'hono'
|
|
import { InferResponseType, InferRequestType } from 'hono'
|
|
|
import { Navbar } from '@/components/ui/navbar'
|
|
import { Navbar } from '@/components/ui/navbar'
|
|
|
-import { Card } from '@/components/ui/card'
|
|
|
|
|
import { Button } from '@/components/ui/button'
|
|
import { Button } from '@/components/ui/button'
|
|
|
import { useAuth } from '@/utils/auth'
|
|
import { useAuth } from '@/utils/auth'
|
|
|
|
|
+import './index.css'
|
|
|
|
|
|
|
|
type AddressResponse = InferResponseType<typeof deliveryAddressClient.$get, 200>
|
|
type AddressResponse = InferResponseType<typeof deliveryAddressClient.$get, 200>
|
|
|
type Address = AddressResponse['data'][0]
|
|
type Address = AddressResponse['data'][0]
|
|
@@ -18,6 +18,7 @@ export default function AddressManagePage() {
|
|
|
const { user } = useAuth()
|
|
const { user } = useAuth()
|
|
|
const queryClient = useQueryClient()
|
|
const queryClient = useQueryClient()
|
|
|
const [selectedAddressId, setSelectedAddressId] = useState<number | null>(null)
|
|
const [selectedAddressId, setSelectedAddressId] = useState<number | null>(null)
|
|
|
|
|
+ const [swipeStates, setSwipeStates] = useState<Record<number, boolean>>({})
|
|
|
|
|
|
|
|
// 获取地址列表
|
|
// 获取地址列表
|
|
|
const { data: addresses, isLoading } = useQuery({
|
|
const { data: addresses, isLoading } = useQuery({
|
|
@@ -102,10 +103,11 @@ export default function AddressManagePage() {
|
|
|
|
|
|
|
|
// 选择地址并返回
|
|
// 选择地址并返回
|
|
|
const handleSelectAddress = (address: Address) => {
|
|
const handleSelectAddress = (address: Address) => {
|
|
|
|
|
+ closeAllSwipes()
|
|
|
// 检查是否是选择模式
|
|
// 检查是否是选择模式
|
|
|
const pages = Taro.getCurrentPages()
|
|
const pages = Taro.getCurrentPages()
|
|
|
const prevPage = pages[pages.length - 2]
|
|
const prevPage = pages[pages.length - 2]
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (prevPage && prevPage.route === 'pages/order-submit/index') {
|
|
if (prevPage && prevPage.route === 'pages/order-submit/index') {
|
|
|
// 返回订单页面
|
|
// 返回订单页面
|
|
|
prevPage.setData({
|
|
prevPage.setData({
|
|
@@ -115,8 +117,29 @@ export default function AddressManagePage() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 检查是否是选择模式
|
|
|
|
|
+ const isSelectMode = () => {
|
|
|
|
|
+ const pages = Taro.getCurrentPages()
|
|
|
|
|
+ const prevPage = pages[pages.length - 2]
|
|
|
|
|
+ return prevPage && prevPage.route === 'pages/order-submit/index'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 侧滑切换
|
|
|
|
|
+ const handleSwipeToggle = (id: number) => {
|
|
|
|
|
+ setSwipeStates(prev => ({
|
|
|
|
|
+ ...prev,
|
|
|
|
|
+ [id]: !prev[id]
|
|
|
|
|
+ }))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭所有侧滑
|
|
|
|
|
+ const closeAllSwipes = () => {
|
|
|
|
|
+ setSwipeStates({})
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 删除地址
|
|
// 删除地址
|
|
|
const handleDeleteAddress = (id: number) => {
|
|
const handleDeleteAddress = (id: number) => {
|
|
|
|
|
+ closeAllSwipes()
|
|
|
Taro.showModal({
|
|
Taro.showModal({
|
|
|
title: '删除地址',
|
|
title: '删除地址',
|
|
|
content: '确定要删除这个收货地址吗?',
|
|
content: '确定要删除这个收货地址吗?',
|
|
@@ -129,110 +152,135 @@ export default function AddressManagePage() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
- <View className="min-h-screen bg-gray-50 flex flex-col">
|
|
|
|
|
|
|
+ <View className="address-container">
|
|
|
<Navbar
|
|
<Navbar
|
|
|
title="收货地址"
|
|
title="收货地址"
|
|
|
leftIcon="i-heroicons-chevron-left-20-solid"
|
|
leftIcon="i-heroicons-chevron-left-20-solid"
|
|
|
onClickLeft={() => Taro.navigateBack()}
|
|
onClickLeft={() => Taro.navigateBack()}
|
|
|
/>
|
|
/>
|
|
|
-
|
|
|
|
|
- <ScrollView className="flex-1">
|
|
|
|
|
- <View className="px-4 py-4">
|
|
|
|
|
- {isLoading ? (
|
|
|
|
|
- <View className="flex justify-center py-10">
|
|
|
|
|
- <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
|
|
|
|
|
- </View>
|
|
|
|
|
- ) : (
|
|
|
|
|
- <>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <ScrollView className="address-scroll-view">
|
|
|
|
|
+ {isLoading ? (
|
|
|
|
|
+ <View className="loading-container">
|
|
|
|
|
+ <View className="i-heroicons-arrow-path-20-solid animate-spin loading-icon" />
|
|
|
|
|
+ </View>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <View className="address-list">
|
|
|
{addresses?.data?.map((address) => (
|
|
{addresses?.data?.map((address) => (
|
|
|
- <Card key={address.id} className="mb-4">
|
|
|
|
|
- <View className="p-4">
|
|
|
|
|
- <View className="flex items-start justify-between mb-2">
|
|
|
|
|
- <View className="flex-1">
|
|
|
|
|
- <View className="flex items-center mb-1">
|
|
|
|
|
- <Text className="font-medium text-gray-900 mr-2">
|
|
|
|
|
- {address.name}
|
|
|
|
|
- </Text>
|
|
|
|
|
- <Text className="text-sm text-gray-600">
|
|
|
|
|
- {address.phone}
|
|
|
|
|
|
|
+ <View key={address.id} className="address-item">
|
|
|
|
|
+ {/* 侧滑删除区域 */}
|
|
|
|
|
+ <View
|
|
|
|
|
+ className="swipe-cell"
|
|
|
|
|
+ onClick={() => closeAllSwipes()}
|
|
|
|
|
+ >
|
|
|
|
|
+ <View
|
|
|
|
|
+ className="swipe-content"
|
|
|
|
|
+ onClick={(e) => e.stopPropagation()}
|
|
|
|
|
+ >
|
|
|
|
|
+ <View className="address-content">
|
|
|
|
|
+ <View className="address-header">
|
|
|
|
|
+ <Text className="address-name">{address.name}</Text>
|
|
|
|
|
+ <Text className="address-phone">
|
|
|
|
|
+ {address.phone?.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')}
|
|
|
</Text>
|
|
</Text>
|
|
|
{address.isDefault === 1 && (
|
|
{address.isDefault === 1 && (
|
|
|
- <Text className="ml-2 px-2 py-1 bg-red-100 text-red-600 text-xs rounded">
|
|
|
|
|
- 默认
|
|
|
|
|
- </Text>
|
|
|
|
|
|
|
+ <Text className="default-tag">默认</Text>
|
|
|
)}
|
|
)}
|
|
|
</View>
|
|
</View>
|
|
|
-
|
|
|
|
|
- <Text className="text-sm text-gray-700">
|
|
|
|
|
- {address.province?.name || ''}
|
|
|
|
|
- {address.city?.name || ''}
|
|
|
|
|
- {address.district?.name || ''}
|
|
|
|
|
- {address.town?.name || ''}
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <Text className="address-detail">
|
|
|
|
|
+ {address.province?.name || ''}
|
|
|
|
|
+ {address.city?.name || ''}
|
|
|
|
|
+ {address.district?.name || ''}
|
|
|
|
|
+ {address.town?.name || ''}
|
|
|
{address.address}
|
|
{address.address}
|
|
|
</Text>
|
|
</Text>
|
|
|
|
|
+
|
|
|
|
|
+ <View className="address-actions">
|
|
|
|
|
+ {isSelectMode() ? (
|
|
|
|
|
+ <Button
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ className="select-btn"
|
|
|
|
|
+ onClick={() => handleSelectAddress(address)}
|
|
|
|
|
+ >
|
|
|
|
|
+ 选择
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <View className="action-buttons">
|
|
|
|
|
+ {address.isDefault !== 1 && (
|
|
|
|
|
+ <Button
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ className="set-default-btn"
|
|
|
|
|
+ onClick={() => setDefaultMutation.mutate(address.id)}
|
|
|
|
|
+ >
|
|
|
|
|
+ 设为默认
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ <Button
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ className="edit-btn"
|
|
|
|
|
+ onClick={() => handleEditAddress(address)}
|
|
|
|
|
+ >
|
|
|
|
|
+ 编辑
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <Button
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ className="select-btn"
|
|
|
|
|
+ onClick={() => handleSelectAddress(address)}
|
|
|
|
|
+ >
|
|
|
|
|
+ 选择
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </View>
|
|
|
</View>
|
|
</View>
|
|
|
</View>
|
|
</View>
|
|
|
-
|
|
|
|
|
- <View className="flex items-center justify-between pt-3 border-t border-gray-100">
|
|
|
|
|
- <View className="flex space-x-4">
|
|
|
|
|
- {address.isDefault !== 1 && (
|
|
|
|
|
- <Button
|
|
|
|
|
- size="sm"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- onClick={() => setDefaultMutation.mutate(address.id)}
|
|
|
|
|
- >
|
|
|
|
|
- 设为默认
|
|
|
|
|
- </Button>
|
|
|
|
|
- )}
|
|
|
|
|
-
|
|
|
|
|
- <Button
|
|
|
|
|
- size="sm"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- onClick={() => handleEditAddress(address)}
|
|
|
|
|
- >
|
|
|
|
|
- 编辑
|
|
|
|
|
- </Button>
|
|
|
|
|
-
|
|
|
|
|
- <Button
|
|
|
|
|
- size="sm"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- className="text-red-500"
|
|
|
|
|
- onClick={() => handleDeleteAddress(address.id)}
|
|
|
|
|
- >
|
|
|
|
|
- 删除
|
|
|
|
|
- </Button>
|
|
|
|
|
- </View>
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <View
|
|
|
|
|
+ className={`swipe-actions ${swipeStates[address.id] ? 'swipe-open' : ''}`}
|
|
|
|
|
+ onClick={(e) => e.stopPropagation()}
|
|
|
|
|
+ >
|
|
|
<Button
|
|
<Button
|
|
|
- size="sm"
|
|
|
|
|
- onClick={() => handleSelectAddress(address)}
|
|
|
|
|
|
|
+ className="delete-btn"
|
|
|
|
|
+ onClick={() => handleDeleteAddress(address.id)}
|
|
|
>
|
|
>
|
|
|
- 选择
|
|
|
|
|
|
|
+ 删除
|
|
|
</Button>
|
|
</Button>
|
|
|
</View>
|
|
</View>
|
|
|
</View>
|
|
</View>
|
|
|
- </Card>
|
|
|
|
|
- ))}
|
|
|
|
|
-
|
|
|
|
|
- {addresses?.data?.length === 0 && (
|
|
|
|
|
- <View className="flex flex-col items-center py-20">
|
|
|
|
|
- <View className="i-heroicons-map-pin-20-solid w-16 h-16 text-gray-300 mb-4" />
|
|
|
|
|
- <Text className="text-gray-500 mb-4">暂无收货地址</Text>
|
|
|
|
|
</View>
|
|
</View>
|
|
|
- )}
|
|
|
|
|
- </>
|
|
|
|
|
- )}
|
|
|
|
|
- </View>
|
|
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {addresses?.data?.length === 0 && (
|
|
|
|
|
+ <View className="no-address">
|
|
|
|
|
+ <View className="no-address-icon" />
|
|
|
|
|
+ <Text className="no-address-text">暂无收货地址</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
</ScrollView>
|
|
</ScrollView>
|
|
|
|
|
|
|
|
- {/* 底部添加按钮 */}
|
|
|
|
|
- <View className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-3">
|
|
|
|
|
- <Button
|
|
|
|
|
- className="w-full"
|
|
|
|
|
|
|
+ {/* 底部固定添加按钮 */}
|
|
|
|
|
+ <View className="bottom-fixed">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ className={`address-btn ${addresses?.data?.length >= 20 ? 'disabled' : ''}`}
|
|
|
onClick={handleAddAddress}
|
|
onClick={handleAddAddress}
|
|
|
|
|
+ disabled={addresses?.data?.length >= 20}
|
|
|
>
|
|
>
|
|
|
添加新地址
|
|
添加新地址
|
|
|
</Button>
|
|
</Button>
|
|
|
|
|
+ {addresses?.data?.length >= 20 && (
|
|
|
|
|
+ <Text className="limit-tip">最多可添加20个收货地址</Text>
|
|
|
|
|
+ )}
|
|
|
</View>
|
|
</View>
|
|
|
</View>
|
|
</View>
|
|
|
)
|
|
)
|