Files
mp-10550/src/user/address/index.tsx
赵忠林 1ce6381248 feat(api): 添加电子围栏功能并重构仓库模块
- 新增 shopStoreFence 模块,包含完整的CRUD接口和数据模型
- 将 shopWarehouse 重命名为 shopStoreWarehouse 并更新相关接口
- 配置文件中切换API_BASE_URL到生产环境地址
- 地址管理页面标题从"地址管理"改为"配送管理"
- 配送员页面收益描述从"工资收入"改为"本月配送佣金"
- 用户地址列表增加每月修改次数限制逻辑
- 更新地址数据模型增加updateTime字段
- 页面组件中的收货地址文案统一改为配送地址
- 移除用户优惠券页面中不必要的导航链接
2026-02-07 18:52:35 +08:00

194 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;