refactor(customer): 优化客户数据查询和表单字段校验

- 移除新增客户页面对手机号的必填和格式校验
- 修改手机号字段标签为“手机号/微信号”,取消必填和长度限制
- 新增判断当前用户是否为超级管理员逻辑
- 抽取并统一构建客户查询参数方法,根据权限动态设置筛选条件
- 优化客户列表数据获取逻辑,支持超级管理员查看全部客户
- 调整依赖项,更新使用了新构建的查询参数函数
- 增强状态统计接口参数构建,统一调用参数生成函数
- 优化副作用 Hook 依赖,保证数据加载时机正确
This commit is contained in:
2026-06-04 15:21:56 +08:00
parent ca4f1c2a77
commit 812df56f8c
10 changed files with 425 additions and 311 deletions

View File

@@ -3,7 +3,7 @@ import {Loading, CellGroup, Cell, Input, Form, Calendar, Popup, SearchBar} from
import {Edit, Calendar as CalendarIcon, ArrowRight, Del} from '@nutui/icons-react-taro' import {Edit, Calendar as CalendarIcon, ArrowRight, Del} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import {useRouter} from '@tarojs/taro' import {useRouter} from '@tarojs/taro'
import {View, Text} from '@tarojs/components' import {View, Text, ScrollView} from '@tarojs/components'
import FixedButton from "@/components/FixedButton"; import FixedButton from "@/components/FixedButton";
import {ShopDealerApply} from "@/api/shop/shopDealerApply/model"; import {ShopDealerApply} from "@/api/shop/shopDealerApply/model";
import { import {
@@ -699,9 +699,9 @@ const AddShopDealerApply = () => {
position="bottom" position="bottom"
round round
onClose={() => setShowReceptionistPicker(false)} onClose={() => setShowReceptionistPicker(false)}
style={{height: '70%'}} style={{height: '70vh'}}
> >
<View className="flex flex-col h-full"> <View style={{height: '100%', display: 'flex', flexDirection: 'column'}}>
{/* 标题栏 */} {/* 标题栏 */}
<View className="flex items-center justify-between px-4 py-3 border-b border-gray-100"> <View className="flex items-center justify-between px-4 py-3 border-b border-gray-100">
<Text className="text-base font-semibold text-gray-800"></Text> <Text className="text-base font-semibold text-gray-800"></Text>
@@ -718,7 +718,10 @@ const AddShopDealerApply = () => {
/> />
</View> </View>
{/* 列表 */} {/* 列表 */}
<View className="flex-1 overflow-y-auto"> <ScrollView
scrollY
style={{flex: 1, minHeight: 0, height: 'calc(70vh - 112px)'}}
>
{receptionistLoading ? ( {receptionistLoading ? (
<View className="flex justify-center items-center py-8"> <View className="flex justify-center items-center py-8">
<Loading></Loading> <Loading></Loading>
@@ -742,7 +745,7 @@ const AddShopDealerApply = () => {
/> />
)) ))
)} )}
</View> </ScrollView>
</View> </View>
</Popup> </Popup>

View File

@@ -1,4 +1,4 @@
import {useState, useEffect, useCallback} from 'react' import {useState, useEffect, useCallback, useMemo, useRef} from 'react'
import {View, Text} from '@tarojs/components' import {View, Text} from '@tarojs/components'
import Taro, {useDidShow} from '@tarojs/taro' import Taro, {useDidShow} from '@tarojs/taro'
import {Loading, InfiniteLoading, Empty, Space, Tabs, TabPane, Tag, Button, SearchBar} from '@nutui/nutui-react-taro' import {Loading, InfiniteLoading, Empty, Space, Tabs, TabPane, Tag, Button, SearchBar} from '@nutui/nutui-react-taro'
@@ -24,6 +24,10 @@ interface CustomerUser extends UserType {
protectDays?: number; // 剩余保护天数 protectDays?: number; // 剩余保护天数
} }
const PAGE_LIMIT = 10;
const COUNT_LIMIT = 1;
const COUNT_REFRESH_DELAY = 300;
const CustomerIndex = () => { const CustomerIndex = () => {
const [list, setList] = useState<CustomerUser[]>([]) const [list, setList] = useState<CustomerUser[]>([])
const [loading, setLoading] = useState<boolean>(false) const [loading, setLoading] = useState<boolean>(false)
@@ -32,33 +36,58 @@ const CustomerIndex = () => {
const [displaySearchValue, setDisplaySearchValue] = useState<string>('') const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true) const [hasMore, setHasMore] = useState(true)
const [statusCounts, setStatusCounts] = useState({
all: 0,
pending: 0,
signed: 0,
cancelled: 0
});
const requestSeqRef = useRef(0)
const didShowReadyRef = useRef(false)
const countRefreshTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
// 权限检查:只要登录就能查看客户列表 // 权限检查:只要登录就能查看客户列表
const {user, loading: userLoading} = useUser() const {user, loading: userLoading} = useUser()
const roleCheckFinished = !userLoading const roleCheckFinished = !userLoading
const isLoggedIn = roleCheckFinished && user !== null const isLoggedIn = roleCheckFinished && user !== null
const isSuperAdmin = user?.isSuperAdmin === true const isSuperAdmin = user?.isSuperAdmin === true
const isAdminRole = user?.isAdmin === true || user?.roles?.some(role => role.roleCode === 'admin') === true
const canViewAllCustomers = isSuperAdmin || isAdminRole
const currentUserId = Number(Taro.getStorageSync('UserId')) || user?.userId || 0
// Tab配置 // Tab配置
const tabList = getStatusOptions(); const tabList = getStatusOptions();
const buildCustomerQueryParams = useCallback((currentPage?: number) => { const buildCustomerQueryParams = useCallback((currentPage?: number, limit?: number) => {
const currentUserId = Number(Taro.getStorageSync('UserId')) || user?.userId || 0;
const params: any = { const params: any = {
type: 4 type: 4
}; };
if (currentPage !== undefined) { if (currentPage !== undefined) {
params.page = currentPage; params.page = currentPage;
params.limit = limit || PAGE_LIMIT;
} }
if (!isSuperAdmin) { if (!canViewAllCustomers) {
params.userId = currentUserId; params.userId = currentUserId;
params.receptionistId = currentUserId; params.receptionistId = currentUserId;
} }
return params; return params;
}, [isSuperAdmin, user?.userId]); }, [canViewAllCustomers, currentUserId]);
const canHandleCustomer = useCallback((customer: ShopDealerApply) => {
if (isSuperAdmin) return true;
return Number(customer.receptionistId) === currentUserId;
}, [currentUserId, isSuperAdmin]);
const updateStatusCount = useCallback((status: CustomerStatus, count?: number) => {
if (count === undefined) return;
setStatusCounts(prev => ({
...prev,
[status]: count
}));
}, []);
// 复制手机号 // 复制手机号
const copyPhone = (phone: string) => { const copyPhone = (phone: string) => {
@@ -148,12 +177,6 @@ const CustomerIndex = () => {
// 转换为天数,向上取整 // 转换为天数,向上取整
const remainingDays = Math.ceil(remainingMs / (1000 * 60 * 60 * 24)); const remainingDays = Math.ceil(remainingMs / (1000 * 60 * 60 * 24));
console.log('=== 基于过期时间计算 ===');
console.log('过期时间:', expirationTime);
console.log('当前时间:', now.toLocaleString());
console.log('剩余天数:', remainingDays);
console.log('======================');
return Math.max(0, remainingDays); return Math.max(0, remainingDays);
} }
@@ -184,12 +207,6 @@ const CustomerIndex = () => {
// 计算剩余保护天数 // 计算剩余保护天数
const remainingDays = protectionPeriod - daysPassed; const remainingDays = protectionPeriod - daysPassed;
console.log('=== 基于申请时间计算 ===');
console.log('申请时间:', applyTime);
console.log('已过去天数:', daysPassed);
console.log('剩余保护天数:', remainingDays);
console.log('======================');
return Math.max(0, remainingDays); return Math.max(0, remainingDays);
} catch (error) { } catch (error) {
console.error('日期计算错误:', error); console.error('日期计算错误:', error);
@@ -200,19 +217,24 @@ const CustomerIndex = () => {
// 获取客户数据 // 获取客户数据
const fetchCustomerData = useCallback(async (statusFilter?: CustomerStatus, resetPage = false, targetPage?: number) => { const fetchCustomerData = useCallback(async (statusFilter?: CustomerStatus, resetPage = false, targetPage?: number) => {
const requestSeq = ++requestSeqRef.current;
setLoading(true); setLoading(true);
try { try {
const currentPage = resetPage ? 1 : (targetPage || 1); const currentPage = resetPage ? 1 : (targetPage || 1);
// 构建API参数根据状态筛选 // 构建API参数根据状态筛选
// 非管查看自己提交的(userId)或分配给自己的(receptionistId)的客户;管查询全部 // 非管理员查看自己提交的(userId)或分配给自己的(receptionistId)的客户;管理员查询全部
const params = buildCustomerQueryParams(currentPage); const currentStatus = statusFilter || activeTab;
const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab); const params = buildCustomerQueryParams(currentPage, PAGE_LIMIT);
const applyStatus = mapCustomerStatusToApplyStatus(currentStatus);
if (applyStatus !== undefined) { if (applyStatus !== undefined) {
params.applyStatus = applyStatus; params.applyStatus = applyStatus;
} }
const res = await pageShopDealerApply(params); const res = await pageShopDealerApply(params);
if (requestSeq !== requestSeqRef.current) return;
updateStatusCount(currentStatus, res?.count);
if (res?.list && res.list.length > 0) { if (res?.list && res.list.length > 0) {
// 正确映射状态并计算保护天数 // 正确映射状态并计算保护天数
@@ -230,7 +252,7 @@ const CustomerIndex = () => {
} }
// 正确判断是否还有更多数据 // 正确判断是否还有更多数据
const hasMoreData = res.list.length >= 10; // 假设每页10条数据 const hasMoreData = currentPage * PAGE_LIMIT < (res.count || 0);
setHasMore(hasMoreData); setHasMore(hasMoreData);
} else { } else {
if (resetPage || currentPage === 1) { if (resetPage || currentPage === 1) {
@@ -247,9 +269,11 @@ const CustomerIndex = () => {
icon: 'none' icon: 'none'
}); });
} finally { } finally {
setLoading(false); if (requestSeq === requestSeqRef.current) {
setLoading(false);
}
} }
}, [activeTab, buildCustomerQueryParams]); }, [activeTab, buildCustomerQueryParams, updateStatusCount]);
const reloadMore = async () => { const reloadMore = async () => {
if (loading || !hasMore) return; // 防止重复加载 if (loading || !hasMore) return; // 防止重复加载
@@ -268,56 +292,63 @@ const CustomerIndex = () => {
}, [searchValue]); }, [searchValue]);
// 根据搜索条件筛选数据状态筛选已在API层面处理 // 根据搜索条件筛选数据状态筛选已在API层面处理
const getFilteredList = () => { const filteredList = useMemo(() => {
let filteredList = list; const keyword = displaySearchValue.trim().toLowerCase();
if (!keyword) {
return list;
}
// 按搜索关键词筛选 return list.filter(customer =>
if (displaySearchValue.trim()) {
const keyword = displaySearchValue.trim().toLowerCase();
filteredList = filteredList.filter(customer =>
(customer.realName && customer.realName.toLowerCase().includes(keyword)) || (customer.realName && customer.realName.toLowerCase().includes(keyword)) ||
(customer.dealerName && customer.dealerName.toLowerCase().includes(keyword)) || (customer.dealerName && customer.dealerName.toLowerCase().includes(keyword)) ||
(customer.dealerCode && customer.dealerCode.toLowerCase().includes(keyword)) || (customer.dealerCode && customer.dealerCode.toLowerCase().includes(keyword)) ||
(customer.mobile && customer.mobile.includes(keyword)) || (customer.mobile && customer.mobile.includes(keyword)) ||
(customer.userId && customer.userId.toString().includes(keyword)) (customer.userId && customer.userId.toString().includes(keyword))
); );
} }, [displaySearchValue, list]);
return filteredList;
};
// 获取各状态的统计数量
const [statusCounts, setStatusCounts] = useState({
all: 0,
pending: 0,
signed: 0,
cancelled: 0
});
// 获取所有状态的统计数量 // 获取所有状态的统计数量
const fetchStatusCounts = useCallback(async () => { const fetchStatusCounts = useCallback(async (skipStatus?: CustomerStatus) => {
try { try {
const baseParams = buildCustomerQueryParams(); const baseParams = buildCustomerQueryParams();
const countParams = {
...baseParams,
page: 1,
limit: COUNT_LIMIT
};
const countRequestConfigs = [
{status: 'all' as CustomerStatus, params: {...countParams}},
{status: 'pending' as CustomerStatus, params: {...countParams, applyStatus: 10}},
{status: 'signed' as CustomerStatus, params: {...countParams, applyStatus: 20}},
{status: 'cancelled' as CustomerStatus, params: {...countParams, applyStatus: 30}}
].filter(item => item.status !== skipStatus);
// 并行获取各状态的数量 const countResponses = await Promise.all(
const [allRes, pendingRes, signedRes, cancelledRes] = await Promise.all([ countRequestConfigs.map(item => pageShopDealerApply(item.params))
pageShopDealerApply({...baseParams}), // 全部 );
pageShopDealerApply({...baseParams, applyStatus: 10}), // 跟进中
pageShopDealerApply({...baseParams, applyStatus: 20}), // 已签约
pageShopDealerApply({...baseParams, applyStatus: 30}) // 已取消
]);
setStatusCounts({ setStatusCounts(prev => {
all: allRes?.count || 0, const next = {...prev};
pending: pendingRes?.count || 0, countRequestConfigs.forEach((item, index) => {
signed: signedRes?.count || 0, next[item.status] = countResponses[index]?.count || 0;
cancelled: cancelledRes?.count || 0 });
return next;
}); });
} catch (error) { } catch (error) {
console.error('获取状态统计失败:', error); console.error('获取状态统计失败:', error);
} }
}, [buildCustomerQueryParams]); }, [buildCustomerQueryParams]);
const scheduleStatusCountsRefresh = useCallback((skipStatus?: CustomerStatus) => {
if (countRefreshTimerRef.current) {
clearTimeout(countRefreshTimerRef.current);
}
countRefreshTimerRef.current = setTimeout(() => {
fetchStatusCounts(skipStatus).then();
}, COUNT_REFRESH_DELAY);
}, [fetchStatusCounts]);
const getStatusCounts = () => statusCounts; const getStatusCounts = () => statusCounts;
// 取消操作 // 取消操作
@@ -335,7 +366,7 @@ const CustomerIndex = () => {
setPage(1); setPage(1);
setHasMore(true); setHasMore(true);
fetchCustomerData(activeTab, true).then(); fetchCustomerData(activeTab, true).then();
fetchStatusCounts().then(); scheduleStatusCountsRefresh(activeTab);
}) })
}; };
@@ -351,15 +382,18 @@ const CustomerIndex = () => {
setPage(1); setPage(1);
setHasMore(true); setHasMore(true);
fetchCustomerData(activeTab, true).then(); fetchCustomerData(activeTab, true).then();
fetchStatusCounts().then(); scheduleStatusCountsRefresh(activeTab);
}) })
} }
// 初始化统计数据 // 清理延迟统计刷新定时器
useEffect(() => { useEffect(() => {
if (!isLoggedIn) return; return () => {
fetchStatusCounts().then(); if (countRefreshTimerRef.current) {
}, [fetchStatusCounts, isLoggedIn]); clearTimeout(countRefreshTimerRef.current);
}
}
}, []);
// 当activeTab变化时重新获取数据 // 当activeTab变化时重新获取数据
useEffect(() => { useEffect(() => {
@@ -368,155 +402,166 @@ const CustomerIndex = () => {
setPage(1); // 重置页码 setPage(1); // 重置页码
setHasMore(true); // 重置加载状态 setHasMore(true); // 重置加载状态
fetchCustomerData(activeTab, true); fetchCustomerData(activeTab, true);
}, [activeTab, fetchCustomerData, isLoggedIn]); scheduleStatusCountsRefresh(activeTab);
}, [activeTab, fetchCustomerData, isLoggedIn, scheduleStatusCountsRefresh]);
// 监听页面显示,当从其他页面返回时刷新数据 // 监听页面显示,当从其他页面返回时刷新数据
useDidShow(() => { useDidShow(() => {
if (!isLoggedIn) return; if (!isLoggedIn) return;
if (!didShowReadyRef.current) {
didShowReadyRef.current = true;
return;
}
// 刷新当前tab的数据和统计信息 // 刷新当前tab的数据和统计信息
setList([]); setList([]);
setPage(1); setPage(1);
setHasMore(true); setHasMore(true);
fetchCustomerData(activeTab, true); fetchCustomerData(activeTab, true);
fetchStatusCounts(); scheduleStatusCountsRefresh(activeTab);
}); });
// 渲染客户项 // 渲染客户项
const renderCustomerItem = (customer: CustomerUser) => ( const renderCustomerItem = (customer: CustomerUser) => {
<View key={customer.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm"> const canHandle = canHandleCustomer(customer);
<View className="flex items-center mb-3">
<View className="flex-1">
<View className="flex items-center justify-between mb-1">
<Text className="font-semibold text-gray-800 mr-2">
{customer.dealerName}
</Text>
{customer.customerStatus && (
<Tag type={getStatusTagType(customer.customerStatus)}>
{getStatusText(customer.customerStatus)}
</Tag>
)}
</View>
<View className="flex items-center mb-1">
<Space direction="vertical">
<Text className="text-xs text-gray-500">{customer.realName}</Text>
<View className="flex items-center">
<Text className="text-xs text-gray-500" onClick={(e) => {
e.stopPropagation();
makePhoneCall(customer.mobile || '');
}}>{customer.mobile}</Text>
<View className="flex items-center ml-2">
<Phone
size={12}
className="text-green-500 mr-2"
onClick={(e) => {
e.stopPropagation();
makePhoneCall(customer.mobile || '');
}}
/>
<Text
className="text-xs text-blue-500 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
copyPhone(customer.mobile || '');
}}
>
</Text>
</View>
</View>
<Text className="text-xs text-gray-500">
{customer.createTime}
</Text>
</Space>
</View>
{/* 保护天数显示 */} return (
{customer.applyStatus === 10 && ( <View key={customer.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center my-1"> <View className="flex items-center mb-3">
<Text className="text-xs text-gray-500 mr-2"></Text> <View className="flex-1">
{customer.protectDays && customer.protectDays > 0 ? ( <View className="flex items-center justify-between mb-1">
<Text className={`text-xs px-2 py-1 rounded ${ <Text className="font-semibold text-gray-800 mr-2">
customer.protectDays <= 2 {customer.dealerName}
? 'bg-red-100 text-red-600' </Text>
: customer.protectDays <= 4 {customer.customerStatus && (
? 'bg-orange-100 text-orange-600' <Tag type={getStatusTagType(customer.customerStatus)}>
: 'bg-green-100 text-green-600' {getStatusText(customer.customerStatus)}
}`}> </Tag>
{customer.protectDays}
</Text>
) : (
<Text className="text-xs px-2 py-1 rounded bg-gray-100 text-gray-500">
</Text>
)} )}
</View> </View>
)} <View className="flex items-center mb-1">
<Space direction="vertical">
<View className={'flex items-center gap-2'}> <Text className="text-xs text-gray-500">{customer.realName}</Text>
<Text className="text-xs text-gray-500">{customer?.nickName}</Text> <View className="flex items-center">
<AngleDoubleLeft size={12} className={'text-blue-500'} /> <Text className="text-xs text-gray-500" onClick={(e) => {
<Text className={'text-xs text-gray-500'}>{customer?.refereeName}</Text> e.stopPropagation();
</View> makePhoneCall(customer.mobile || '');
}}>{customer.mobile}</Text>
{/* 接待人员 */} <View className="flex items-center ml-2">
{customer.receptionistName && ( <Phone
<View className="flex items-center my-1"> size={12}
<Text className="text-xs text-gray-500">{customer.receptionistName}</Text> className="text-green-500 mr-2"
onClick={(e) => {
e.stopPropagation();
makePhoneCall(customer.mobile || '');
}}
/>
<Text
className="text-xs text-blue-500 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
copyPhone(customer.mobile || '');
}}
>
</Text>
</View>
</View>
<Text className="text-xs text-gray-500">
{customer.createTime}
</Text>
</Space>
</View> </View>
)}
{/* 显示 comments 字段 */} {/* 保护天数显示 */}
<Space className="flex items-center"> {customer.applyStatus === 10 && (
<Text className="text-xs text-gray-500">{customer.comments || '暂无'}</Text> <View className="flex items-center my-1">
<Text <Text className="text-xs text-gray-500 mr-2"></Text>
className="text-xs text-blue-500 cursor-pointer" {customer.protectDays && customer.protectDays > 0 ? (
onClick={(e) => { <Text className={`text-xs px-2 py-1 rounded ${
e.stopPropagation(); customer.protectDays <= 2
editComments(customer); ? 'bg-red-100 text-red-600'
}} : customer.protectDays <= 4
> ? 'bg-orange-100 text-orange-600'
: 'bg-green-100 text-green-600'
</Text> }`}>
</Space> {customer.protectDays}
</Text>
) : (
<Text className="text-xs px-2 py-1 rounded bg-gray-100 text-gray-500">
</Text>
)}
</View>
)}
<View className={'flex items-center gap-2'}>
<Text className="text-xs text-gray-500">{customer?.nickName}</Text>
<AngleDoubleLeft size={12} className={'text-blue-500'} />
<Text className={'text-xs text-gray-500'}>{customer?.refereeName}</Text>
</View>
{/* 接待人员 */}
{customer.receptionistName && (
<View className="flex items-center my-1">
<Text className="text-xs text-gray-500">{customer.receptionistName}</Text>
</View>
)}
{/* 显示 comments 字段 */}
<Space className="flex items-center">
<Text className="text-xs text-gray-500">{customer.comments || '暂无'}</Text>
{canHandle && (
<Text
className="text-xs text-blue-500 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
editComments(customer);
}}
>
</Text>
)}
</Space>
</View>
</View> </View>
</View>
{/* 跟进中状态显示操作按钮 */} {/* 跟进中状态仅接待人员/超管显示操作按钮 */}
{(customer.applyStatus === 10 && customer.userId == Taro.getStorageSync('UserId')) && ( {(customer.applyStatus === 10 && canHandle) && (
<Space className="flex justify-end"> <Space className="flex justify-end">
<Button <Button
size="small" size="small"
onClick={() => navTo(`/dealer/customer/add?id=${customer.applyId}`, true)} onClick={() => navTo(`/dealer/customer/add?id=${customer.applyId}`, true)}
style={{marginRight: '8px', backgroundColor: '#52c41a', color: 'white'}} style={{marginRight: '8px', backgroundColor: '#52c41a', color: 'white'}}
> >
</Button> </Button>
<Button <Button
size="small" size="small"
onClick={() => handleCancel(customer)} onClick={() => handleCancel(customer)}
style={{backgroundColor: '#ff4d4f', color: 'white'}} style={{backgroundColor: '#ff4d4f', color: 'white'}}
> >
</Button> </Button>
</Space> </Space>
)} )}
{(customer.applyStatus === 30 && customer.userId == Taro.getStorageSync('UserId')) && ( {(customer.applyStatus === 30 && canHandle) && (
<Space className="flex justify-end"> <Space className="flex justify-end">
<Button <Button
size="small" size="small"
onClick={() => handleDelete(customer)} onClick={() => handleDelete(customer)}
style={{backgroundColor: '#ff4d4f', color: 'white'}} style={{backgroundColor: '#ff4d4f', color: 'white'}}
> >
</Button> </Button>
</Space> </Space>
)} )}
</View> </View>
); );
};
// 渲染客户列表 // 渲染客户列表
const renderCustomerList = () => { const renderCustomerList = () => {
const filteredList = getFilteredList();
const isSearching = displaySearchValue.trim().length > 0; const isSearching = displaySearchValue.trim().length > 0;
return ( return (

View File

@@ -151,15 +151,14 @@ function UserCard() {
} }
return ( return (
<View className={'header-bg pt-20'}> <View className={'header-bg'}>
<View className={'p-4'}> <View className={'user-card-wrap'}>
<View <View
className={'user-card w-full flex flex-col justify-around rounded-xl shadow-sm'} className={'w-full flex flex-col justify-around'}
style={{ style={{
background: 'linear-gradient(to bottom, #ffffff, #ffffff)', // 这种情况建议使用类名来控制样式(引入外联样式)
// width: '720rpx', // width: '720rpx',
// margin: '10px auto 0px auto', // margin: '10px auto 0px auto',
height: '120px', height: '124px',
// borderRadius: '22px 22px 0 0', // borderRadius: '22px 22px 0 0',
}} }}
> >
@@ -174,8 +173,8 @@ function UserCard() {
</Button> </Button>
) )
} }
<View className={'user-info flex flex-col px-2'}> <View className={'user-info flex flex-col px-3'}>
<View className={'py-1 text-black font-bold max-w-28'}>{getDisplayName()}</View> <View className={'user-card__name py-1 font-bold max-w-28'}>{getDisplayName()}</View>
{isLoggedIn ? ( {isLoggedIn ? (
<View className={'grade text-xs py-0'}> <View className={'grade text-xs py-0'}>
<Tag type="success">{getRoleName()}</Tag> <Tag type="success">{getRoleName()}</Tag>
@@ -184,8 +183,8 @@ function UserCard() {
</View> </View>
</View> </View>
<View className={'gap-2 flex items-center'}> <View className={'gap-2 flex items-center'}>
{isAdmin() && <Scan className={'text-gray-900'} size={24} onClick={() => navTo('/user/store/verification', true)} />} {isAdmin() && <Scan className={'user-card__scan'} size={24} onClick={() => navTo('/user/store/verification', true)} />}
<View className={'mr-4 text-sm px-3 py-1 text-black border-gray-400 border-solid border-2 rounded-3xl'} <View className={'user-card__profile mr-4 text-sm px-3 py-1'}
onClick={() => navTo('/user/profile/profile', true)}> onClick={() => navTo('/user/profile/profile', true)}>
{'个人资料'} {'个人资料'}
</View> </View>

View File

@@ -2,7 +2,7 @@ import {Cell} from '@nutui/nutui-react-taro'
import navTo from "@/utils/common"; import navTo from "@/utils/common";
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import {View, Text} from '@tarojs/components' import {View, Text} from '@tarojs/components'
import {ArrowRight, LogisticsError, Tips, Ask} from '@nutui/icons-react-taro' import {ArrowRight, LogisticsError} from '@nutui/icons-react-taro'
import {useUser} from '@/hooks/useUser' import {useUser} from '@/hooks/useUser'
const UserCell = () => { const UserCell = () => {
@@ -26,13 +26,36 @@ const UserCell = () => {
return ( return (
<> <>
<View className={'px-4'}> <View className={'p-4'}>
<Cell.Group divider={true} description={ <Cell.Group divider={true}>
<View style={{display: 'inline-flex', alignItems: 'center'}}> <Cell
<Text style={{marginTop: '12px'}}></Text> className="nutui-cell-clickable"
</View> title="后台管理"
}> align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => Taro.reLaunch({url: '/pages/index/index'})}
/>
</Cell.Group>
<Cell.Group divider={true}>
<Cell
className="nutui-cell-clickable"
title="个人资料"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/user/profile/profile', true)}
/>
</Cell.Group>
<Cell.Group divider={true}>
<Cell
className="nutui-cell-clickable"
title="退出登录"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={onLogout}
/>
</Cell.Group>
<Cell <Cell
className="nutui-cell-clickable" className="nutui-cell-clickable"
style={{ style={{
@@ -81,62 +104,42 @@ const UserCell = () => {
{/* navTo('/user/userVerify/index', true)*/} {/* navTo('/user/userVerify/index', true)*/}
{/* }}*/} {/* }}*/}
{/*/>*/} {/*/>*/}
<Cell {/* <Cell*/}
className="nutui-cell-clickable" {/* className="nutui-cell-clickable"*/}
title={ {/* title={*/}
<View style={{display: 'inline-flex', alignItems: 'center'}}> {/* <View style={{display: 'inline-flex', alignItems: 'center'}}>*/}
<Ask size={16}/> {/* <Ask size={16}/>*/}
<Text className={'pl-3 text-sm'}></Text> {/* <Text className={'pl-3 text-sm'}>常见问题</Text>*/}
</View> {/* </View>*/}
} {/* }*/}
align="center" {/* align="center"*/}
extra={<ArrowRight color="#cccccc" size={18}/>} {/* extra={<ArrowRight color="#cccccc" size={18}/>}*/}
onClick={() => { {/* onClick={() => {*/}
navTo('/user/help/index') {/* navTo('/user/help/index')*/}
}} {/* }}*/}
/> {/* />*/}
<Cell {/* <Cell*/}
className="nutui-cell-clickable" {/* className="nutui-cell-clickable"*/}
title={ {/* title={*/}
<View style={{display: 'inline-flex', alignItems: 'center'}}> {/* <View style={{display: 'inline-flex', alignItems: 'center'}}>*/}
<Tips size={16}/> {/* <Tips size={16}/>*/}
<Text className={'pl-3 text-sm'}></Text> {/* <Text className={'pl-3 text-sm'}>关于我们</Text>*/}
</View> {/* </View>*/}
} {/* }*/}
align="center" {/* align="center"*/}
extra={<ArrowRight color="#cccccc" size={18}/>} {/* extra={<ArrowRight color="#cccccc" size={18}/>}*/}
onClick={() => { {/* onClick={() => {*/}
navTo('/user/about/index') {/* navTo('/user/about/index')*/}
}} {/* }}*/}
/> {/* />*/}
</Cell.Group> {/*</Cell.Group>*/}
<Cell.Group divider={true} description={ {/*<Cell.Group divider={true} description={*/}
<View style={{display: 'inline-flex', alignItems: 'center'}}> {/* <View style={{display: 'inline-flex', alignItems: 'center'}}>*/}
<Text style={{marginTop: '12px'}}></Text> {/* <Text style={{marginTop: '12px'}}>账号管理</Text>*/}
</View> {/* </View>*/}
}> {/*}>*/}
<Cell
className="nutui-cell-clickable" {/*</Cell.Group>*/}
title="个人资料"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/user/profile/profile', true)}
/>
<Cell
className="nutui-cell-clickable"
title="返回首页"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => Taro.reLaunch({url: '/pages/index/index'})}
/>
<Cell
className="nutui-cell-clickable"
title="退出登录"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={onLogout}
/>
</Cell.Group>
</View> </View>
</> </>
) )

View File

@@ -46,7 +46,7 @@ const UserFooter = () => {
return ( return (
<> <>
<div className={'text-center py-4 w-full text-gray-300'} onClick={onLoginByPhone}> <div className={'text-center py-4 w-full text-gray-300 fixed bottom-2'} onClick={onLoginByPhone}>
<div className={'text-xs text-gray-400 py-1'}>{Version}</div> <div className={'text-xs text-gray-400 py-1'}>{Version}</div>
<div className={'text-xs text-gray-400 py-1'}>Copyright © { new Date().getFullYear() } {Copyright}</div> <div className={'text-xs text-gray-400 py-1'}>Copyright © { new Date().getFullYear() } {Copyright}</div>
</div> </div>

View File

@@ -1,5 +1,4 @@
export default definePageConfig({ export default definePageConfig({
navigationBarTitleText: '我的', navigationBarTitleText: '我的',
navigationStyle: 'custom',
navigationBarBackgroundColor: '#e9fff2' navigationBarBackgroundColor: '#e9fff2'
}) })

View File

@@ -1,4 +1,73 @@
.header-bg{ .user-page {
background: url('https://oss.wsdns.cn/20250621/edb5d4da976b4d97ba185cb7077d2858.jpg') no-repeat top center; min-height: 100vh;
background-size: 100%; background: linear-gradient(180deg, #f2fbf6 0%, #f8fbff 38%, #f9fafb 100%);
}
.header-bg {
position: relative;
overflow: hidden;
padding-top: 12px;
background:
linear-gradient(90deg, rgba(34, 197, 94, 0.14) 0%, rgba(56, 189, 248, 0.12) 52%, rgba(245, 158, 11, 0.08) 100%),
linear-gradient(135deg, #e8f8ef 0%, #eef7ff 58%, #f9fafb 100%);
}
.header-bg::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #22c55e 0%, #38bdf8 55%, #f59e0b 100%);
}
.user-card-wrap {
position: relative;
z-index: 1;
padding: 16px;
}
.user-card {
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 18px !important;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(247, 252, 255, 0.94) 100%);
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.08);
}
.user-card-header {
height: 100%;
padding-top: 0 !important;
}
.user-card .nut-avatar {
border: 3px solid rgba(255, 255, 255, 0.94);
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.12);
}
.user-card__name {
max-width: 150px;
overflow: hidden;
color: #0f172a;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-card__profile {
border: 1px solid rgba(15, 118, 110, 0.22);
border-radius: 999px;
background: #ecfdf5;
color: #0f766e;
white-space: nowrap;
}
.user-card__scan {
padding: 6px;
border-radius: 999px;
background: #ecfeff;
color: #0f766e;
}
.grade .nut-tag {
border-radius: 999px;
} }

View File

@@ -1,26 +1,26 @@
import {useEffect, useState} from 'react' import {useEffect} from 'react'
import Taro from '@tarojs/taro'; // import Taro from '@tarojs/taro';
import UserCard from "./components/UserCard"; import UserCard from "./components/UserCard";
import UserOrder from "./components/UserOrder"; // import UserOrder from "./components/UserOrder";
import UserCell from "./components/UserCell"; import UserCell from "./components/UserCell";
import UserFooter from "./components/UserFooter"; import UserFooter from "./components/UserFooter";
import {useUser} from "@/hooks/useUser"; import {useUser} from "@/hooks/useUser";
import {NavBar} from '@nutui/nutui-react-taro'; // import {NavBar} from '@nutui/nutui-react-taro';
import {Home} from '@nutui/icons-react-taro' // import {Home} from '@nutui/icons-react-taro';
import './user.scss' import './user.scss';
function User() { function User() {
const [statusBarHeight, setStatusBarHeight] = useState<number>() // const [statusBarHeight, setStatusBarHeight] = useState<number>()
const { const {
isAdmin isAdmin
} = useUser(); } = useUser();
useEffect(() => { useEffect(() => {
Taro.getSystemInfo({ // Taro.getSystemInfo({
success: (res) => { // success: (res) => {
setStatusBarHeight(res.statusBarHeight) // setStatusBarHeight(res.statusBarHeight)
}, // },
}) // })
}, []); }, []);
/** /**
@@ -28,11 +28,9 @@ function User() {
*/ */
if (isAdmin()) { if (isAdmin()) {
return <> return <>
<div className={'w-full'} style={{ <div className={'user-page w-full'}>
background: 'linear-gradient(to bottom, #e9fff2, #f9fafb)'
}}>
<UserCard/> <UserCard/>
<UserOrder/> {/*<UserOrder/>*/}
<UserCell/> <UserCell/>
<UserFooter/> <UserFooter/>
</div> </div>
@@ -41,31 +39,29 @@ function User() {
return ( return (
<> <>
<NavBar {/*<NavBar*/}
fixed={true} {/* fixed={true}*/}
style={{marginTop: `${statusBarHeight}px`, backgroundColor: 'transparent'}} {/* style={{marginTop: `${statusBarHeight}px`, backgroundColor: 'transparent'}}*/}
onBackClick={() => { {/* onBackClick={() => {*/}
}} {/* }}*/}
left={ {/* left={*/}
<div style={{ {/* <div style={{*/}
display: 'flex', {/* display: 'flex',*/}
alignItems: 'center', {/* alignItems: 'center',*/}
justifyContent: 'center', {/* justifyContent: 'center',*/}
width: '30px', {/* width: '30px',*/}
height: '30px', {/* height: '30px',*/}
backgroundColor: 'white', {/* backgroundColor: 'white',*/}
textAlign: 'center', {/* textAlign: 'center',*/}
borderRadius: '50%', {/* borderRadius: '50%',*/}
}}> {/* }}>*/}
<Home size={15} onClick={() => Taro.reLaunch({url: '/pages/index/index'})} style={{color: 'black', marginBottom: '1px'}} /> {/* <Home size={15} onClick={() => Taro.reLaunch({url: '/pages/index/index'})} style={{color: 'black', marginBottom: '1px'}} />*/}
</div> {/* </div>*/}
} {/* }*/}
> {/*>*/}
<span></span> {/* <span>我的</span>*/}
</NavBar> {/*</NavBar>*/}
<div className={'w-full'} style={{ <div className={'user-page w-full'}>
background: 'linear-gradient(to bottom, #e9fff2, #f9fafb)'
}}>
<UserCard/> <UserCard/>
{/*<UserOrder/>*/} {/*<UserOrder/>*/}
<UserCell/> <UserCell/>

Binary file not shown.

BIN
系统使用说明书.docx Normal file

Binary file not shown.