diff --git a/src/dealer/customer/add.tsx b/src/dealer/customer/add.tsx index 97edffd..c3f178a 100644 --- a/src/dealer/customer/add.tsx +++ b/src/dealer/customer/add.tsx @@ -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 Taro 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 {ShopDealerApply} from "@/api/shop/shopDealerApply/model"; import { @@ -699,9 +699,9 @@ const AddShopDealerApply = () => { position="bottom" round onClose={() => setShowReceptionistPicker(false)} - style={{height: '70%'}} + style={{height: '70vh'}} > - + {/* 标题栏 */} 选择接待人员 @@ -718,7 +718,10 @@ const AddShopDealerApply = () => { /> {/* 列表 */} - + {receptionistLoading ? ( 加载中 @@ -742,7 +745,7 @@ const AddShopDealerApply = () => { /> )) )} - + diff --git a/src/dealer/customer/index.tsx b/src/dealer/customer/index.tsx index f0bc028..4e51100 100644 --- a/src/dealer/customer/index.tsx +++ b/src/dealer/customer/index.tsx @@ -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 Taro, {useDidShow} from '@tarojs/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; // 剩余保护天数 } +const PAGE_LIMIT = 10; +const COUNT_LIMIT = 1; +const COUNT_REFRESH_DELAY = 300; + const CustomerIndex = () => { const [list, setList] = useState([]) const [loading, setLoading] = useState(false) @@ -32,33 +36,58 @@ const CustomerIndex = () => { const [displaySearchValue, setDisplaySearchValue] = useState('') const [page, setPage] = useState(1) 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 | null>(null) // 权限检查:只要登录就能查看客户列表 const {user, loading: userLoading} = useUser() const roleCheckFinished = !userLoading const isLoggedIn = roleCheckFinished && user !== null 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配置 const tabList = getStatusOptions(); - const buildCustomerQueryParams = useCallback((currentPage?: number) => { - const currentUserId = Number(Taro.getStorageSync('UserId')) || user?.userId || 0; + const buildCustomerQueryParams = useCallback((currentPage?: number, limit?: number) => { const params: any = { type: 4 }; if (currentPage !== undefined) { params.page = currentPage; + params.limit = limit || PAGE_LIMIT; } - if (!isSuperAdmin) { + if (!canViewAllCustomers) { params.userId = currentUserId; params.receptionistId = currentUserId; } 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) => { @@ -148,12 +177,6 @@ const CustomerIndex = () => { // 转换为天数,向上取整 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); } @@ -184,12 +207,6 @@ const CustomerIndex = () => { // 计算剩余保护天数 const remainingDays = protectionPeriod - daysPassed; - console.log('=== 基于申请时间计算 ==='); - console.log('申请时间:', applyTime); - console.log('已过去天数:', daysPassed); - console.log('剩余保护天数:', remainingDays); - console.log('======================'); - return Math.max(0, remainingDays); } catch (error) { console.error('日期计算错误:', error); @@ -200,19 +217,24 @@ const CustomerIndex = () => { // 获取客户数据 const fetchCustomerData = useCallback(async (statusFilter?: CustomerStatus, resetPage = false, targetPage?: number) => { + const requestSeq = ++requestSeqRef.current; setLoading(true); try { const currentPage = resetPage ? 1 : (targetPage || 1); // 构建API参数,根据状态筛选 - // 非超管查看自己提交的(userId)或分配给自己的(receptionistId)的客户;超管查询全部 - const params = buildCustomerQueryParams(currentPage); - const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab); + // 非管理员查看自己提交的(userId)或分配给自己的(receptionistId)的客户;管理员查询全部 + const currentStatus = statusFilter || activeTab; + const params = buildCustomerQueryParams(currentPage, PAGE_LIMIT); + const applyStatus = mapCustomerStatusToApplyStatus(currentStatus); if (applyStatus !== undefined) { params.applyStatus = applyStatus; } const res = await pageShopDealerApply(params); + if (requestSeq !== requestSeqRef.current) return; + + updateStatusCount(currentStatus, res?.count); 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); } else { if (resetPage || currentPage === 1) { @@ -247,9 +269,11 @@ const CustomerIndex = () => { icon: 'none' }); } finally { - setLoading(false); + if (requestSeq === requestSeqRef.current) { + setLoading(false); + } } - }, [activeTab, buildCustomerQueryParams]); + }, [activeTab, buildCustomerQueryParams, updateStatusCount]); const reloadMore = async () => { if (loading || !hasMore) return; // 防止重复加载 @@ -268,56 +292,63 @@ const CustomerIndex = () => { }, [searchValue]); // 根据搜索条件筛选数据(状态筛选已在API层面处理) - const getFilteredList = () => { - let filteredList = list; + const filteredList = useMemo(() => { + const keyword = displaySearchValue.trim().toLowerCase(); + if (!keyword) { + return list; + } - // 按搜索关键词筛选 - if (displaySearchValue.trim()) { - const keyword = displaySearchValue.trim().toLowerCase(); - filteredList = filteredList.filter(customer => + return list.filter(customer => (customer.realName && customer.realName.toLowerCase().includes(keyword)) || (customer.dealerName && customer.dealerName.toLowerCase().includes(keyword)) || (customer.dealerCode && customer.dealerCode.toLowerCase().includes(keyword)) || (customer.mobile && customer.mobile.includes(keyword)) || (customer.userId && customer.userId.toString().includes(keyword)) - ); - } - - return filteredList; - }; - - // 获取各状态的统计数量 - const [statusCounts, setStatusCounts] = useState({ - all: 0, - pending: 0, - signed: 0, - cancelled: 0 - }); + ); + }, [displaySearchValue, list]); // 获取所有状态的统计数量 - const fetchStatusCounts = useCallback(async () => { + const fetchStatusCounts = useCallback(async (skipStatus?: CustomerStatus) => { try { 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 [allRes, pendingRes, signedRes, cancelledRes] = await Promise.all([ - pageShopDealerApply({...baseParams}), // 全部 - pageShopDealerApply({...baseParams, applyStatus: 10}), // 跟进中 - pageShopDealerApply({...baseParams, applyStatus: 20}), // 已签约 - pageShopDealerApply({...baseParams, applyStatus: 30}) // 已取消 - ]); + const countResponses = await Promise.all( + countRequestConfigs.map(item => pageShopDealerApply(item.params)) + ); - setStatusCounts({ - all: allRes?.count || 0, - pending: pendingRes?.count || 0, - signed: signedRes?.count || 0, - cancelled: cancelledRes?.count || 0 + setStatusCounts(prev => { + const next = {...prev}; + countRequestConfigs.forEach((item, index) => { + next[item.status] = countResponses[index]?.count || 0; + }); + return next; }); } catch (error) { console.error('获取状态统计失败:', error); } }, [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; // 取消操作 @@ -335,7 +366,7 @@ const CustomerIndex = () => { setPage(1); setHasMore(true); fetchCustomerData(activeTab, true).then(); - fetchStatusCounts().then(); + scheduleStatusCountsRefresh(activeTab); }) }; @@ -351,15 +382,18 @@ const CustomerIndex = () => { setPage(1); setHasMore(true); fetchCustomerData(activeTab, true).then(); - fetchStatusCounts().then(); + scheduleStatusCountsRefresh(activeTab); }) } - // 初始化统计数据 + // 清理延迟统计刷新定时器 useEffect(() => { - if (!isLoggedIn) return; - fetchStatusCounts().then(); - }, [fetchStatusCounts, isLoggedIn]); + return () => { + if (countRefreshTimerRef.current) { + clearTimeout(countRefreshTimerRef.current); + } + } + }, []); // 当activeTab变化时重新获取数据 useEffect(() => { @@ -368,155 +402,166 @@ const CustomerIndex = () => { setPage(1); // 重置页码 setHasMore(true); // 重置加载状态 fetchCustomerData(activeTab, true); - }, [activeTab, fetchCustomerData, isLoggedIn]); + scheduleStatusCountsRefresh(activeTab); + }, [activeTab, fetchCustomerData, isLoggedIn, scheduleStatusCountsRefresh]); // 监听页面显示,当从其他页面返回时刷新数据 useDidShow(() => { if (!isLoggedIn) return; + if (!didShowReadyRef.current) { + didShowReadyRef.current = true; + return; + } + // 刷新当前tab的数据和统计信息 setList([]); setPage(1); setHasMore(true); fetchCustomerData(activeTab, true); - fetchStatusCounts(); + scheduleStatusCountsRefresh(activeTab); }); // 渲染客户项 - const renderCustomerItem = (customer: CustomerUser) => ( - - - - - - {customer.dealerName} - - {customer.customerStatus && ( - - {getStatusText(customer.customerStatus)} - - )} - - - - 联系人:{customer.realName} - - { - e.stopPropagation(); - makePhoneCall(customer.mobile || ''); - }}>联系电话:{customer.mobile} - - { - e.stopPropagation(); - makePhoneCall(customer.mobile || ''); - }} - /> - { - e.stopPropagation(); - copyPhone(customer.mobile || ''); - }} - > - 复制 - - - - - 添加时间:{customer.createTime} - - - + const renderCustomerItem = (customer: CustomerUser) => { + const canHandle = canHandleCustomer(customer); - {/* 保护天数显示 */} - {customer.applyStatus === 10 && ( - - 保护期: - {customer.protectDays && customer.protectDays > 0 ? ( - - 剩余{customer.protectDays}天 - - ) : ( - - 已过期 - + return ( + + + + + + {customer.dealerName} + + {customer.customerStatus && ( + + {getStatusText(customer.customerStatus)} + )} - )} - - - 报备人:{customer?.nickName} - - {customer?.refereeName} - - - {/* 接待人员 */} - {customer.receptionistName && ( - - 接待人员:{customer.receptionistName} + + + 联系人:{customer.realName} + + { + e.stopPropagation(); + makePhoneCall(customer.mobile || ''); + }}>联系电话:{customer.mobile} + + { + e.stopPropagation(); + makePhoneCall(customer.mobile || ''); + }} + /> + { + e.stopPropagation(); + copyPhone(customer.mobile || ''); + }} + > + 复制 + + + + + 添加时间:{customer.createTime} + + - )} - {/* 显示 comments 字段 */} - - 跟进情况:{customer.comments || '暂无'} - { - e.stopPropagation(); - editComments(customer); - }} - > - 编辑 - - + {/* 保护天数显示 */} + {customer.applyStatus === 10 && ( + + 保护期: + {customer.protectDays && customer.protectDays > 0 ? ( + + 剩余{customer.protectDays}天 + + ) : ( + + 已过期 + + )} + + )} + + + 报备人:{customer?.nickName} + + {customer?.refereeName} + + + {/* 接待人员 */} + {customer.receptionistName && ( + + 接待人员:{customer.receptionistName} + + )} + + {/* 显示 comments 字段 */} + + 跟进情况:{customer.comments || '暂无'} + {canHandle && ( + { + e.stopPropagation(); + editComments(customer); + }} + > + 编辑 + + )} + + - - {/* 跟进中状态显示操作按钮 */} - {(customer.applyStatus === 10 && customer.userId == Taro.getStorageSync('UserId')) && ( - - - - - )} - {(customer.applyStatus === 30 && customer.userId == Taro.getStorageSync('UserId')) && ( - - - - )} - - ); + {/* 跟进中状态仅接待人员/超管显示操作按钮 */} + {(customer.applyStatus === 10 && canHandle) && ( + + + + + )} + {(customer.applyStatus === 30 && canHandle) && ( + + + + )} + + ); + }; // 渲染客户列表 const renderCustomerList = () => { - const filteredList = getFilteredList(); const isSearching = displaySearchValue.trim().length > 0; return ( diff --git a/src/pages/user/components/UserCard.tsx b/src/pages/user/components/UserCard.tsx index 259307f..a1dc10e 100644 --- a/src/pages/user/components/UserCard.tsx +++ b/src/pages/user/components/UserCard.tsx @@ -151,15 +151,14 @@ function UserCard() { } return ( - - + + @@ -174,8 +173,8 @@ function UserCard() { ) } - - {getDisplayName()} + + {getDisplayName()} {isLoggedIn ? ( {getRoleName()} @@ -184,8 +183,8 @@ function UserCard() { - {isAdmin() && navTo('/user/store/verification', true)} />} - navTo('/user/store/verification', true)} />} + navTo('/user/profile/profile', true)}> {'个人资料'} diff --git a/src/pages/user/components/UserCell.tsx b/src/pages/user/components/UserCell.tsx index ee1e473..3bd0bef 100644 --- a/src/pages/user/components/UserCell.tsx +++ b/src/pages/user/components/UserCell.tsx @@ -2,7 +2,7 @@ import {Cell} from '@nutui/nutui-react-taro' import navTo from "@/utils/common"; import Taro from '@tarojs/taro' 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' const UserCell = () => { @@ -26,13 +26,36 @@ const UserCell = () => { return ( <> - + - - 我的服务 - - }> + + } + onClick={() => Taro.reLaunch({url: '/pages/index/index'})} + /> + + + + } + onClick={() => navTo('/user/profile/profile', true)} + /> + + + } + onClick={onLogout} + /> + { {/* navTo('/user/userVerify/index', true)*/} {/* }}*/} {/*/>*/} - - - 常见问题 - - } - align="center" - extra={} - onClick={() => { - navTo('/user/help/index') - }} - /> - - - 关于我们 - - } - align="center" - extra={} - onClick={() => { - navTo('/user/about/index') - }} - /> - - - 账号管理 - - }> - } - onClick={() => navTo('/user/profile/profile', true)} - /> - } - onClick={() => Taro.reLaunch({url: '/pages/index/index'})} - /> - } - onClick={onLogout} - /> - + {/* */} + {/* */} + {/* 常见问题*/} + {/* */} + {/* }*/} + {/* align="center"*/} + {/* extra={}*/} + {/* onClick={() => {*/} + {/* navTo('/user/help/index')*/} + {/* }}*/} + {/* />*/} + {/* */} + {/* */} + {/* 关于我们*/} + {/* */} + {/* }*/} + {/* align="center"*/} + {/* extra={}*/} + {/* onClick={() => {*/} + {/* navTo('/user/about/index')*/} + {/* }}*/} + {/* />*/} + {/**/} + {/**/} + {/* 账号管理*/} + {/* */} + {/*}>*/} + + {/**/} ) diff --git a/src/pages/user/components/UserFooter.tsx b/src/pages/user/components/UserFooter.tsx index fb74b71..94400fa 100644 --- a/src/pages/user/components/UserFooter.tsx +++ b/src/pages/user/components/UserFooter.tsx @@ -46,7 +46,7 @@ const UserFooter = () => { return ( <> -
+
当前版本:{Version}
Copyright © { new Date().getFullYear() } {Copyright}
diff --git a/src/pages/user/user.config.ts b/src/pages/user/user.config.ts index 31e4776..090f19c 100644 --- a/src/pages/user/user.config.ts +++ b/src/pages/user/user.config.ts @@ -1,5 +1,4 @@ export default definePageConfig({ navigationBarTitleText: '我的', - navigationStyle: 'custom', navigationBarBackgroundColor: '#e9fff2' }) diff --git a/src/pages/user/user.scss b/src/pages/user/user.scss index 0e2c161..2f683d0 100644 --- a/src/pages/user/user.scss +++ b/src/pages/user/user.scss @@ -1,4 +1,73 @@ -.header-bg{ - background: url('https://oss.wsdns.cn/20250621/edb5d4da976b4d97ba185cb7077d2858.jpg') no-repeat top center; - background-size: 100%; +.user-page { + min-height: 100vh; + 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; } diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx index bd0351a..237ff85 100644 --- a/src/pages/user/user.tsx +++ b/src/pages/user/user.tsx @@ -1,26 +1,26 @@ -import {useEffect, useState} from 'react' -import Taro from '@tarojs/taro'; +import {useEffect} from 'react' +// import Taro from '@tarojs/taro'; import UserCard from "./components/UserCard"; -import UserOrder from "./components/UserOrder"; +// import UserOrder from "./components/UserOrder"; import UserCell from "./components/UserCell"; import UserFooter from "./components/UserFooter"; import {useUser} from "@/hooks/useUser"; -import {NavBar} from '@nutui/nutui-react-taro'; -import {Home} from '@nutui/icons-react-taro' -import './user.scss' +// import {NavBar} from '@nutui/nutui-react-taro'; +// import {Home} from '@nutui/icons-react-taro'; +import './user.scss'; function User() { - const [statusBarHeight, setStatusBarHeight] = useState() + // const [statusBarHeight, setStatusBarHeight] = useState() const { isAdmin } = useUser(); useEffect(() => { - Taro.getSystemInfo({ - success: (res) => { - setStatusBarHeight(res.statusBarHeight) - }, - }) + // Taro.getSystemInfo({ + // success: (res) => { + // setStatusBarHeight(res.statusBarHeight) + // }, + // }) }, []); /** @@ -28,11 +28,9 @@ function User() { */ if (isAdmin()) { return <> -
+
- + {/**/}
@@ -41,31 +39,29 @@ function User() { return ( <> - { - }} - left={ -
- Taro.reLaunch({url: '/pages/index/index'})} style={{color: 'black', marginBottom: '1px'}} /> -
- } - > - 我的 -
-
+ {/* {*/} + {/* }}*/} + {/* left={*/} + {/*
*/} + {/* Taro.reLaunch({url: '/pages/index/index'})} style={{color: 'black', marginBottom: '1px'}} />*/} + {/*
*/} + {/* }*/} + {/*>*/} + {/* 我的*/} + {/**/} +
{/**/} diff --git a/小程序系统使用说明书.docx b/小程序系统使用说明书.docx new file mode 100644 index 0000000..9b73d87 Binary files /dev/null and b/小程序系统使用说明书.docx differ diff --git a/系统使用说明书.docx b/系统使用说明书.docx new file mode 100644 index 0000000..0b5479e Binary files /dev/null and b/系统使用说明书.docx differ