- 新增 shopStoreFence 模块,包含完整的CRUD接口和数据模型 - 将 shopWarehouse 重命名为 shopStoreWarehouse 并更新相关接口 - 配置文件中切换API_BASE_URL到生产环境地址 - 地址管理页面标题从"地址管理"改为"配送管理" - 配送员页面收益描述从"工资收入"改为"本月配送佣金" - 用户地址列表增加每月修改次数限制逻辑 - 更新地址数据模型增加updateTime字段 - 页面组件中的收货地址文案统一改为配送地址 - 移除用户优惠券页面中不必要的导航链接
194 lines
6.1 KiB
TypeScript
194 lines
6.1 KiB
TypeScript
import {useState} from "react";
|
||
import Taro, {useDidShow} from '@tarojs/taro'
|
||
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Divider} from '@nutui/nutui-react-taro'
|
||
import {Dongdong, ArrowRight, CheckNormal, Checked} from '@nutui/icons-react-taro'
|
||
import {View} from '@tarojs/components'
|
||
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
||
import {listShopUserAddress, removeShopUserAddress, updateShopUserAddress} from "@/api/shop/shopUserAddress";
|
||
import FixedButton from "@/components/FixedButton";
|
||
import dayjs from "dayjs";
|
||
|
||
const Address = () => {
|
||
const [list, setList] = useState<ShopUserAddress[]>([])
|
||
const [address, setAddress] = useState<ShopUserAddress>()
|
||
|
||
const parseTime = (raw?: unknown) => {
|
||
if (raw === undefined || raw === null || raw === '') return null;
|
||
// 兼容秒/毫秒时间戳
|
||
if (typeof raw === 'number' || (typeof raw === 'string' && /^\d+$/.test(raw))) {
|
||
const n = Number(raw);
|
||
return dayjs(Number.isFinite(n) ? (n < 1e12 ? n * 1000 : n) : raw as any);
|
||
}
|
||
return dayjs(raw as any);
|
||
}
|
||
|
||
const canModifyOncePerMonth = (item: ShopUserAddress) => {
|
||
const lastUpdate = parseTime(item.updateTime);
|
||
if (!lastUpdate || !lastUpdate.isValid()) return { ok: true as const };
|
||
|
||
// 若 updateTime 与 createTime 基本一致,则视为“未修改过”,不做限制
|
||
const createdAt = parseTime(item.createTime);
|
||
if (createdAt && createdAt.isValid() && Math.abs(lastUpdate.diff(createdAt, 'minute')) < 1) {
|
||
return { ok: true as const };
|
||
}
|
||
|
||
const nextAllowed = lastUpdate.add(1, 'month');
|
||
const now = dayjs();
|
||
if (now.isBefore(nextAllowed)) {
|
||
return { ok: false as const, nextAllowed: nextAllowed.format('YYYY-MM-DD HH:mm') };
|
||
}
|
||
return { ok: true as const };
|
||
}
|
||
|
||
const reload = () => {
|
||
listShopUserAddress({
|
||
userId: Taro.getStorageSync('UserId')
|
||
})
|
||
.then(data => {
|
||
setList(data || [])
|
||
// 默认地址
|
||
setAddress(data.find(item => item.isDefault))
|
||
})
|
||
.catch(() => {
|
||
Taro.showToast({
|
||
title: '获取地址失败',
|
||
icon: 'error'
|
||
});
|
||
})
|
||
}
|
||
|
||
const onDefault = async (item: ShopUserAddress) => {
|
||
if (address) {
|
||
await updateShopUserAddress({
|
||
...address,
|
||
isDefault: false
|
||
})
|
||
}
|
||
await updateShopUserAddress({
|
||
id: item.id,
|
||
isDefault: true
|
||
})
|
||
Taro.showToast({
|
||
title: '设置成功',
|
||
icon: 'success'
|
||
});
|
||
reload();
|
||
}
|
||
|
||
const onDel = async (id?: number) => {
|
||
await removeShopUserAddress(id)
|
||
Taro.showToast({
|
||
title: '删除成功',
|
||
icon: 'success'
|
||
});
|
||
reload();
|
||
}
|
||
|
||
const selectAddress = async (item: ShopUserAddress) => {
|
||
if (address) {
|
||
await updateShopUserAddress({
|
||
...address,
|
||
isDefault: false
|
||
})
|
||
}
|
||
await updateShopUserAddress({
|
||
id: item.id,
|
||
isDefault: true
|
||
})
|
||
setTimeout(() => {
|
||
Taro.navigateBack()
|
||
}, 500)
|
||
}
|
||
|
||
useDidShow(() => {
|
||
reload()
|
||
});
|
||
|
||
if (list.length == 0) {
|
||
return (
|
||
<ConfigProvider>
|
||
<div className={'h-full flex flex-col justify-center items-center'} style={{
|
||
height: 'calc(100vh - 300px)',
|
||
}}>
|
||
<Empty
|
||
style={{
|
||
backgroundColor: 'transparent'
|
||
}}
|
||
description="您还没有地址哦"
|
||
/>
|
||
<Space>
|
||
<Button onClick={() => Taro.navigateTo({url: '/user/address/add'})}>新增地址</Button>
|
||
<Button type="success" fill="dashed"
|
||
onClick={() => Taro.navigateTo({url: '/user/address/wxAddress'})}>获取微信地址</Button>
|
||
</Space>
|
||
</div>
|
||
</ConfigProvider>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<CellGroup>
|
||
<Cell
|
||
onClick={() => Taro.navigateTo({url: '/user/address/wxAddress'})}
|
||
>
|
||
<div className={'flex justify-between items-center w-full'}>
|
||
<div className={'flex items-center gap-3'}>
|
||
<Dongdong className={'text-green-600'}/>
|
||
<div>获取微信地址</div>
|
||
</div>
|
||
<ArrowRight className={'text-gray-400'}/>
|
||
</div>
|
||
</Cell>
|
||
</CellGroup>
|
||
{list.map((item, _) => (
|
||
<Cell.Group>
|
||
<Cell className={'flex flex-col gap-1'} onClick={() => selectAddress(item)}>
|
||
<View>
|
||
<View className={'font-medium text-sm'}>{item.name} {item.phone}</View>
|
||
</View>
|
||
<View className={'text-xs'}>
|
||
{item.province} {item.city} {item.region} {item.address}
|
||
</View>
|
||
</Cell>
|
||
<Cell
|
||
align="center"
|
||
title={
|
||
<View className={'flex items-center gap-1'} onClick={() => onDefault(item)}>
|
||
{item.isDefault ? <Checked className={'text-green-600'} size={16}/> : <CheckNormal size={16}/>}
|
||
<View className={'text-gray-400'}>默认地址</View>
|
||
</View>
|
||
}
|
||
extra={
|
||
<>
|
||
<View className={'text-gray-400'} onClick={() => onDel(item.id)}>
|
||
删除
|
||
</View>
|
||
<Divider direction={'vertical'}/>
|
||
<View className={'text-gray-400'}
|
||
onClick={() => {
|
||
const { ok, nextAllowed } = canModifyOncePerMonth(item);
|
||
if (!ok) {
|
||
Taro.showToast({
|
||
title: `一个月只能修改一次${nextAllowed ? ',' + nextAllowed + ' 后可再次修改' : ''}`,
|
||
icon: 'none',
|
||
});
|
||
return;
|
||
}
|
||
Taro.navigateTo({url: '/user/address/add?id=' + item.id})
|
||
}}>
|
||
修改
|
||
</View>
|
||
</>
|
||
}
|
||
/>
|
||
</Cell.Group>
|
||
))}
|
||
{/* 底部浮动按钮 */}
|
||
<FixedButton text={'新增地址'} onClick={() => Taro.navigateTo({url: '/user/address/add'})} />
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default Address;
|