feat(customer): 优化报备人管理和权限控制
- 移除页面分页查询,仅保留精确查询接口调用 - 添加当前登录用户ID获取和报备人规范化处理逻辑 - 实现管理员可查看全部客户,普通分销商仅查看自己的权限控制 - 集成用户角色显示功能,在用户卡片组件中展示角色标签 - 修复角色名称获取逻辑,支持多种数据
This commit is contained in:
@@ -15,7 +15,7 @@ import {
|
|||||||
extractDateForCalendar, formatDateForDisplay
|
extractDateForCalendar, formatDateForDisplay
|
||||||
} from "@/utils/dateUtils";
|
} from "@/utils/dateUtils";
|
||||||
import {ShopDealerUser} from "@/api/shop/shopDealerUser/model";
|
import {ShopDealerUser} from "@/api/shop/shopDealerUser/model";
|
||||||
import {getShopDealerUser, pageShopDealerUser} from "@/api/shop/shopDealerUser";
|
import {getShopDealerUser} from "@/api/shop/shopDealerUser";
|
||||||
|
|
||||||
const AddShopDealerApply = () => {
|
const AddShopDealerApply = () => {
|
||||||
const {params} = useRouter();
|
const {params} = useRouter();
|
||||||
@@ -286,6 +286,8 @@ const AddShopDealerApply = () => {
|
|||||||
const submitSucceed = async (values: any) => {
|
const submitSucceed = async (values: any) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const currentUserId = Number(Taro.getStorageSync('UserId')) || 0;
|
||||||
|
|
||||||
// 房号相关必填校验
|
// 房号相关必填校验
|
||||||
if (!values.address || values.address.trim() === '') {
|
if (!values.address || values.address.trim() === '') {
|
||||||
Taro.showToast({title: '请选择/填写小区', icon: 'error'});
|
Taro.showToast({title: '请选择/填写小区', icon: 'error'});
|
||||||
@@ -323,18 +325,34 @@ const AddShopDealerApply = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证报备人是否存在
|
// 规范化报备人:留空=自己报备(当前登录用户)
|
||||||
if (values.userId > 0) {
|
const rawUserId = normalizeText(values.userId);
|
||||||
const isExist = await pageShopDealerUser({userId: Number(values.userId)});
|
const submitUserId = rawUserId
|
||||||
if(isExist && isExist.count == 0){
|
? Number(rawUserId)
|
||||||
Taro.showToast({
|
: (isEditMode ? (existingApply?.userId || currentUserId) : currentUserId);
|
||||||
title: '报备人不存在',
|
if (!Number.isFinite(submitUserId) || submitUserId <= 0) {
|
||||||
icon: 'error'
|
Taro.showToast({title: '请填写正确的报备人ID', icon: 'error'});
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报备人存在性校验 + 获取该报备人的推荐人(用于后端展示链路)
|
||||||
|
let reporterDealerUser: ShopDealerUser | undefined = undefined;
|
||||||
|
if (submitUserId === currentUserId) {
|
||||||
|
reporterDealerUser = referee;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
reporterDealerUser = await getShopDealerUser(submitUserId);
|
||||||
|
} catch {
|
||||||
|
Taro.showToast({title: '报备人不存在', icon: 'error'});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 后端常用 0 表示“无推荐人”,避免传空值触发“推荐人不存在”
|
||||||
|
const submitRefereeId = (reporterDealerUser?.refereeId && reporterDealerUser.refereeId > 0)
|
||||||
|
? reporterDealerUser.refereeId
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const houseKeyRaw = buildHouseKey(values.address, values.buildingNo, values.unitNo, values.roomNo);
|
const houseKeyRaw = buildHouseKey(values.address, values.buildingNo, values.unitNo, values.roomNo);
|
||||||
const houseKeyNormalized = buildHouseKeyNormalized(values.address, values.buildingNo, values.unitNo, values.roomNo);
|
const houseKeyNormalized = buildHouseKeyNormalized(values.address, values.buildingNo, values.unitNo, values.roomNo);
|
||||||
const houseKey = houseKeyNormalized || houseKeyRaw;
|
const houseKey = houseKeyNormalized || houseKeyRaw;
|
||||||
@@ -416,7 +434,10 @@ const AddShopDealerApply = () => {
|
|||||||
// 客户姓名/手机号
|
// 客户姓名/手机号
|
||||||
realName: values.realName,
|
realName: values.realName,
|
||||||
mobile: values.mobile,
|
mobile: values.mobile,
|
||||||
refereeId: referee?.refereeId,
|
// 报备人(留空时用当前登录用户)
|
||||||
|
// userId: submitUserId,
|
||||||
|
// 推荐人(报备人的上级;无则传 0)
|
||||||
|
refereeId: submitRefereeId,
|
||||||
applyStatus: isEditMode ? 20 : 10,
|
applyStatus: isEditMode ? 20 : 10,
|
||||||
auditTime: undefined,
|
auditTime: undefined,
|
||||||
// 设置保护期过期时间(15天后)
|
// 设置保护期过期时间(15天后)
|
||||||
@@ -496,8 +517,7 @@ const AddShopDealerApply = () => {
|
|||||||
unitNo: parsed.unitNo,
|
unitNo: parsed.unitNo,
|
||||||
roomNo: parsed.roomNo,
|
roomNo: parsed.roomNo,
|
||||||
realName: FormData.realName,
|
realName: FormData.realName,
|
||||||
mobile: FormData.mobile,
|
mobile: FormData.mobile
|
||||||
userId: FormData.userId
|
|
||||||
});
|
});
|
||||||
}, [FormData]);
|
}, [FormData]);
|
||||||
|
|
||||||
@@ -571,13 +591,13 @@ const AddShopDealerApply = () => {
|
|||||||
{/*</Form.Item>*/}
|
{/*</Form.Item>*/}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Form.Item name="userId" label="报备人(ID)" initialValue={FormData?.userId}>
|
{/*<Form.Item name="userId" label="报备人(ID)" initialValue={FormData?.userId}>*/}
|
||||||
<Input
|
{/* <Input*/}
|
||||||
placeholder="自己报备请留空"
|
{/* placeholder="自己报备请留空"*/}
|
||||||
disabled={isEditMode}
|
{/* disabled={isEditMode}*/}
|
||||||
type="number"
|
{/* type="number"*/}
|
||||||
/>
|
{/* />*/}
|
||||||
</Form.Item>
|
{/*</Form.Item>*/}
|
||||||
</CellGroup>
|
</CellGroup>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,10 @@ const CustomerIndex = () => {
|
|||||||
const [hasMore, setHasMore] = useState(true)
|
const [hasMore, setHasMore] = useState(true)
|
||||||
|
|
||||||
// 非分销商不允许查看客户列表
|
// 非分销商不允许查看客户列表
|
||||||
const {hasRole, loading: userLoading} = useUser()
|
const {user, hasRole, loading: userLoading} = useUser()
|
||||||
const canView = hasRole('dealer')
|
// 管理员允许查看全部;普通分销商仅查看自己
|
||||||
|
const isAdminUser = user?.isAdmin === true
|
||||||
|
const canView = hasRole('dealer') || isAdminUser
|
||||||
const roleCheckFinished = !userLoading
|
const roleCheckFinished = !userLoading
|
||||||
const noPermissionShownRef = useRef(false)
|
const noPermissionShownRef = useRef(false)
|
||||||
|
|
||||||
@@ -196,12 +198,17 @@ const CustomerIndex = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const currentPage = resetPage ? 1 : (targetPage || page);
|
const currentPage = resetPage ? 1 : (targetPage || page);
|
||||||
|
const currentUserId = Number(Taro.getStorageSync('UserId')) || user?.userId || 0;
|
||||||
|
|
||||||
// 构建API参数,根据状态筛选
|
// 构建API参数,根据状态筛选
|
||||||
const params: any = {
|
const params: any = {
|
||||||
type: 4,
|
type: 4,
|
||||||
page: currentPage
|
page: currentPage
|
||||||
};
|
};
|
||||||
|
// 非管理员:只看自己添加的客户
|
||||||
|
if (!isAdminUser && currentUserId > 0) {
|
||||||
|
params.userId = currentUserId;
|
||||||
|
}
|
||||||
const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab);
|
const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab);
|
||||||
if (applyStatus !== undefined) {
|
if (applyStatus !== undefined) {
|
||||||
params.applyStatus = applyStatus;
|
params.applyStatus = applyStatus;
|
||||||
@@ -244,7 +251,7 @@ const CustomerIndex = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [activeTab, page]);
|
}, [activeTab, page, isAdminUser, user?.userId]);
|
||||||
|
|
||||||
const reloadMore = async () => {
|
const reloadMore = async () => {
|
||||||
if (loading || !hasMore) return; // 防止重复加载
|
if (loading || !hasMore) return; // 防止重复加载
|
||||||
@@ -292,12 +299,19 @@ const CustomerIndex = () => {
|
|||||||
// 获取所有状态的统计数量
|
// 获取所有状态的统计数量
|
||||||
const fetchStatusCounts = useCallback(async () => {
|
const fetchStatusCounts = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
const currentUserId = Number(Taro.getStorageSync('UserId')) || user?.userId || 0;
|
||||||
|
const baseParams: any = {type: 4};
|
||||||
|
// 非管理员:只统计自己添加的客户
|
||||||
|
if (!isAdminUser && currentUserId > 0) {
|
||||||
|
baseParams.userId = currentUserId;
|
||||||
|
}
|
||||||
|
|
||||||
// 并行获取各状态的数量
|
// 并行获取各状态的数量
|
||||||
const [allRes, pendingRes, signedRes, cancelledRes] = await Promise.all([
|
const [allRes, pendingRes, signedRes, cancelledRes] = await Promise.all([
|
||||||
pageShopDealerApply({type: 4}), // 全部
|
pageShopDealerApply({...baseParams}), // 全部
|
||||||
pageShopDealerApply({applyStatus: 10, type: 4}), // 跟进中
|
pageShopDealerApply({...baseParams, applyStatus: 10}), // 跟进中
|
||||||
pageShopDealerApply({applyStatus: 20, type: 4}), // 已签约
|
pageShopDealerApply({...baseParams, applyStatus: 20}), // 已签约
|
||||||
pageShopDealerApply({applyStatus: 30, type: 4}) // 已取消
|
pageShopDealerApply({...baseParams, applyStatus: 30}) // 已取消
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setStatusCounts({
|
setStatusCounts({
|
||||||
@@ -309,7 +323,7 @@ const CustomerIndex = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取状态统计失败:', error);
|
console.error('获取状态统计失败:', error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [isAdminUser, user?.userId]);
|
||||||
|
|
||||||
const getStatusCounts = () => statusCounts;
|
const getStatusCounts = () => statusCounts;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export const useUser = () => {
|
|||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [userInfo] = useState<User>()
|
|
||||||
|
|
||||||
// 自动登录(通过OpenID)
|
// 自动登录(通过OpenID)
|
||||||
const autoLoginByOpenId = async () => {
|
const autoLoginByOpenId = async () => {
|
||||||
@@ -259,7 +258,8 @@ export const useUser = () => {
|
|||||||
|
|
||||||
// 角色名称:优先取用户 roles 数组的第一个角色名称
|
// 角色名称:优先取用户 roles 数组的第一个角色名称
|
||||||
const getRoleName = () => {
|
const getRoleName = () => {
|
||||||
return userInfo?.roles?.[0]?.roleName || userInfo?.roleName || '业务员'
|
// Some APIs return `roles`, some return single `roleName`.
|
||||||
|
return user?.roles?.[0]?.roleName || user?.roleName || '注册用户'
|
||||||
}
|
}
|
||||||
// 检查用户是否已实名认证
|
// 检查用户是否已实名认证
|
||||||
const isCertified = () => {
|
const isCertified = () => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {Button} from '@nutui/nutui-react-taro'
|
import {Avatar, Button, Tag} from '@nutui/nutui-react-taro'
|
||||||
import {Avatar} from '@nutui/nutui-react-taro'
|
|
||||||
import {View} from '@tarojs/components'
|
import {View} from '@tarojs/components'
|
||||||
import {Scan} from '@nutui/icons-react-taro';
|
import {Scan} from '@nutui/icons-react-taro';
|
||||||
import {getWxOpenId} from '@/api/layout';
|
import {getWxOpenId} from '@/api/layout';
|
||||||
@@ -16,7 +15,8 @@ function UserCard() {
|
|||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
loginUser,
|
loginUser,
|
||||||
fetchUserInfo,
|
fetchUserInfo,
|
||||||
getDisplayName
|
getDisplayName,
|
||||||
|
getRoleName
|
||||||
} = useUser();
|
} = useUser();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -178,9 +178,7 @@ function UserCard() {
|
|||||||
<View className={'py-1 text-black font-bold max-w-28'}>{getDisplayName()}</View>
|
<View className={'py-1 text-black font-bold max-w-28'}>{getDisplayName()}</View>
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
<View className={'grade text-xs py-0'}>
|
<View className={'grade text-xs py-0'}>
|
||||||
{/*<Text className={'p-1'}>*/}
|
<Tag type="success">{getRoleName()}</Tag>
|
||||||
{/* {getRoleName()}*/}
|
|
||||||
{/*</Text>*/}
|
|
||||||
</View>
|
</View>
|
||||||
) : ''}
|
) : ''}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user