|
|
@@ -0,0 +1,284 @@
|
|
|
+import { View, ScrollView, Text } from '@tarojs/components'
|
|
|
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
|
+import { useForm } from 'react-hook-form'
|
|
|
+import { zodResolver } from '@hookform/resolvers/zod'
|
|
|
+import { z } from 'zod'
|
|
|
+import { useState, useEffect } from 'react'
|
|
|
+import Taro from '@tarojs/taro'
|
|
|
+import { deliveryAddressClient, cityClient } from '@/api'
|
|
|
+import { InferResponseType, InferRequestType } from 'hono'
|
|
|
+import { Navbar } from '@/components/ui/navbar'
|
|
|
+import { Card } from '@/components/ui/card'
|
|
|
+import { Button } from '@/components/ui/button'
|
|
|
+import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form'
|
|
|
+import { Input } from '@/components/ui/input'
|
|
|
+import { useAuth } from '@/utils/auth'
|
|
|
+
|
|
|
+type Address = InferResponseType<typeof deliveryAddressClient[':id']['$get'], 200>
|
|
|
+type CreateAddressRequest = InferRequestType<typeof deliveryAddressClient.$post>['json']
|
|
|
+type UpdateAddressRequest = InferRequestType<typeof deliveryAddressClient[':id']['$put']>['json']
|
|
|
+
|
|
|
+const addressSchema = z.object({
|
|
|
+ name: z.string().min(1, '请输入收货人姓名'),
|
|
|
+ phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号'),
|
|
|
+ province: z.number().positive('请选择省份'),
|
|
|
+ city: z.number().positive('请选择城市'),
|
|
|
+ district: z.number().positive('请选择区县'),
|
|
|
+ address: z.string().min(1, '请输入详细地址'),
|
|
|
+ isDefault: z.boolean().optional()
|
|
|
+})
|
|
|
+
|
|
|
+type AddressFormData = z.infer<typeof addressSchema>
|
|
|
+
|
|
|
+export default function AddressEditPage() {
|
|
|
+ const { user } = useAuth()
|
|
|
+ const queryClient = useQueryClient()
|
|
|
+ const [addressId, setAddressId] = useState<number | null>(null)
|
|
|
+ const [provinces, setProvinces] = useState<any[]>([])
|
|
|
+ const [cities, setCities] = useState<any[]>([])
|
|
|
+ const [districts, setDistricts] = useState<any[]>([])
|
|
|
+
|
|
|
+ // 获取地址ID
|
|
|
+ useEffect(() => {
|
|
|
+ const params = Taro.getCurrentInstance().router?.params
|
|
|
+ if (params?.id) {
|
|
|
+ setAddressId(parseInt(params.id))
|
|
|
+ }
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ // 获取地址详情
|
|
|
+ const { data: address } = useQuery({
|
|
|
+ queryKey: ['address', addressId],
|
|
|
+ queryFn: async () => {
|
|
|
+ if (!addressId) return null
|
|
|
+ const response = await deliveryAddressClient[':id'].$get({
|
|
|
+ param: { id: addressId }
|
|
|
+ })
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取地址失败')
|
|
|
+ }
|
|
|
+ return response.json()
|
|
|
+ },
|
|
|
+ enabled: !!addressId,
|
|
|
+ })
|
|
|
+
|
|
|
+ // 获取省份
|
|
|
+ const { data: provinceData } = useQuery({
|
|
|
+ queryKey: ['provinces'],
|
|
|
+ queryFn: async () => {
|
|
|
+ const response = await cityClient.$get({
|
|
|
+ query: {
|
|
|
+ page: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ filters: JSON.stringify({ parentId: 0 })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取省份失败')
|
|
|
+ }
|
|
|
+ return response.json()
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 获取城市
|
|
|
+ const fetchCities = async (provinceId: number) => {
|
|
|
+ const response = await cityClient.$get({
|
|
|
+ query: {
|
|
|
+ page: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ filters: JSON.stringify({ parentId: provinceId })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取城市失败')
|
|
|
+ }
|
|
|
+ return response.json()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取区县
|
|
|
+ const fetchDistricts = async (cityId: number) => {
|
|
|
+ const response = await cityClient.$get({
|
|
|
+ query: {
|
|
|
+ page: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ filters: JSON.stringify({ parentId: cityId })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取区县失败')
|
|
|
+ }
|
|
|
+ return response.json()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 表单设置
|
|
|
+ const form = useForm<AddressFormData>({
|
|
|
+ resolver: zodResolver(addressSchema),
|
|
|
+ defaultValues: {
|
|
|
+ name: '',
|
|
|
+ phone: '',
|
|
|
+ province: 0,
|
|
|
+ city: 0,
|
|
|
+ district: 0,
|
|
|
+ address: '',
|
|
|
+ isDefault: false
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 填充表单数据
|
|
|
+ useEffect(() => {
|
|
|
+ if (address) {
|
|
|
+ form.reset({
|
|
|
+ name: address.name,
|
|
|
+ phone: address.phone,
|
|
|
+ province: address.receiverProvince,
|
|
|
+ city: address.receiverCity,
|
|
|
+ district: address.receiverDistrict,
|
|
|
+ address: address.address,
|
|
|
+ isDefault: address.isDefault === 1
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }, [address, form])
|
|
|
+
|
|
|
+ // 创建/更新地址
|
|
|
+ const saveAddressMutation = useMutation({
|
|
|
+ mutationFn: async (data: AddressFormData) => {
|
|
|
+ const addressData: any = {
|
|
|
+ name: data.name,
|
|
|
+ phone: data.phone,
|
|
|
+ receiverProvince: data.province,
|
|
|
+ receiverCity: data.city,
|
|
|
+ receiverDistrict: data.district,
|
|
|
+ address: data.address,
|
|
|
+ userId: user?.id,
|
|
|
+ isDefault: data.isDefault ? 1 : 0
|
|
|
+ }
|
|
|
+
|
|
|
+ let response
|
|
|
+ if (addressId) {
|
|
|
+ response = await deliveryAddressClient[':id']['$put']({
|
|
|
+ param: { id: addressId },
|
|
|
+ json: addressData
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ response = await deliveryAddressClient.$post({ json: addressData })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response.status !== 200 && response.status !== 201) {
|
|
|
+ throw new Error('保存地址失败')
|
|
|
+ }
|
|
|
+ return response.json()
|
|
|
+ },
|
|
|
+ onSuccess: () => {
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['delivery-addresses'] })
|
|
|
+ Taro.showToast({
|
|
|
+ title: addressId ? '更新成功' : '添加成功',
|
|
|
+ icon: 'success'
|
|
|
+ })
|
|
|
+ Taro.navigateBack()
|
|
|
+ },
|
|
|
+ onError: (error) => {
|
|
|
+ Taro.showToast({
|
|
|
+ title: error.message || '保存失败',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 处理省份变化
|
|
|
+ const handleProvinceChange = async (provinceId: number) => {
|
|
|
+ if (provinceId) {
|
|
|
+ const response = await fetchCities(provinceId)
|
|
|
+ setCities(response.data || [])
|
|
|
+ setDistricts([])
|
|
|
+ form.setValue('city', 0)
|
|
|
+ form.setValue('district', 0)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理城市变化
|
|
|
+ const handleCityChange = async (cityId: number) => {
|
|
|
+ if (cityId) {
|
|
|
+ const response = await fetchDistricts(cityId)
|
|
|
+ setDistricts(response.data || [])
|
|
|
+ form.setValue('district', 0)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化省份
|
|
|
+ useEffect(() => {
|
|
|
+ if (provinceData?.data) {
|
|
|
+ setProvinces(provinceData.data)
|
|
|
+ }
|
|
|
+ }, [provinceData])
|
|
|
+
|
|
|
+ // 加载城市数据
|
|
|
+ useEffect(() => {
|
|
|
+ const provinceId = form.watch('province')
|
|
|
+ if (provinceId) {
|
|
|
+ handleProvinceChange(provinceId)
|
|
|
+ }
|
|
|
+ }, [form.watch('province')])
|
|
|
+
|
|
|
+ // 加载区县数据
|
|
|
+ useEffect(() => {
|
|
|
+ const cityId = form.watch('city')
|
|
|
+ if (cityId) {
|
|
|
+ handleCityChange(cityId)
|
|
|
+ }
|
|
|
+ }, [form.watch('city')])
|
|
|
+
|
|
|
+ const onSubmit = (data: AddressFormData) => {
|
|
|
+ saveAddressMutation.mutate(data)
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <View className="min-h-screen bg-gray-50">
|
|
|
+ <Navbar
|
|
|
+ title={addressId ? '编辑地址' : '添加地址'}
|
|
|
+ leftIcon="i-heroicons-chevron-left-20-solid"
|
|
|
+ onClickLeft={() => Taro.navigateBack()}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ScrollView className="h-screen pt-12 pb-20">
|
|
|
+ <View className="px-4 py-4">
|
|
|
+ <Form {...form}>
|
|
|
+ <View className="space-y-4">
|
|
|
+ <Card>
|
|
|
+ <View className="p-4 space-y-4">
|
|
|
+ <FormField
|
|
|
+ name="name"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>收货人姓名</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入收货人姓名" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ name="phone"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>手机号码</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入手机号码" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ name="province"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>省份</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <picker
|
|
|
+ mode="selector"
|
|
|
+ range={provinces}
|
|
|
+ range-key="name"
|
|
|
+ value={provin
|