From 5164d2ec46de750da2c4491498bfd3b7a283f2ae Mon Sep 17 00:00:00 2001 From: menxipeng Date: Sat, 29 Nov 2025 17:34:10 +0800 Subject: [PATCH] youhua --- .../pages/CompanyPermissionsPage.tsx | 268 ++++++++++++++---- src/components/pages/EquipmentPage.tsx | 41 +-- src/components/pages/MerchantsPage.tsx | 141 +++++++-- src/components/pages/WorkOrdersPage.tsx | 4 +- 4 files changed, 330 insertions(+), 124 deletions(-) diff --git a/src/components/pages/CompanyPermissionsPage.tsx b/src/components/pages/CompanyPermissionsPage.tsx index 48c22fc..09690a0 100644 --- a/src/components/pages/CompanyPermissionsPage.tsx +++ b/src/components/pages/CompanyPermissionsPage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react" +import React, { useState, useEffect, useRef } from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card" import { Button } from "../ui/button" import { Input } from "../ui/input" @@ -74,6 +74,22 @@ export default function CompanyPermissionsPage() { const [selectedUser, setSelectedUser] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) const [currentUserRole, setCurrentUserRole] = useState("") + const [userRoleFromApi, setUserRoleFromApi] = useState("") + const hasInitializedCommonRole = useRef(false) + const lastFetchedRoleFilter = useRef("") + const lastFetchedPage = useRef(0) + + // 获取当前用户角色 + const fetchUserRole = async () => { + try { + const result = await apiGet('/back/getUserRole') + if (result.code === 200 && result.data) { + setUserRoleFromApi(result.data) + } + } catch (error) { + console.error('获取用户角色失败:', error) + } + } // 获取角色列表 const fetchRoles = async () => { @@ -157,22 +173,53 @@ export default function CompanyPermissionsPage() { // 初始化数据 useEffect(() => { + fetchUserRole() fetchRoles() }, []) + // 初始化 common 角色的过滤器设置(只执行一次) + useEffect(() => { + if (userRoleFromApi === "common" && roles.length > 0 && !hasInitializedCommonRole.current) { + hasInitializedCommonRole.current = true + setRoleFilter("mall") + setActiveTab("mall") + } + }, [userRoleFromApi, roles]) + // 当角色列表加载完成后,获取用户数据 useEffect(() => { if (roles.length > 0) { - if (roleFilter === "all") { - fetchAllUsers() - } else { - const role = roles.find(r => r.roleKey === roleFilter) - if (role) { - fetchUsersByRole(role.roleId, currentPage) + // 如果用户角色是 common,强制显示商场管理员 + if (userRoleFromApi === "common") { + const mallRole = roles.find(r => r.roleKey === "mall") + if (mallRole) { + // 避免重复请求:检查是否与上次请求的参数相同 + const currentFilter = "mall" + if (lastFetchedRoleFilter.current !== currentFilter || lastFetchedPage.current !== currentPage) { + lastFetchedRoleFilter.current = currentFilter + lastFetchedPage.current = currentPage + fetchUsersByRole(mallRole.roleId, currentPage) + } + } + } else if (userRoleFromApi !== "") { + // 正常情况:根据 roleFilter 获取用户数据 + // 只有当 userRoleFromApi 已经获取到值时才执行 + // 避免重复请求:检查是否与上次请求的参数相同 + if (lastFetchedRoleFilter.current !== roleFilter || lastFetchedPage.current !== currentPage) { + lastFetchedRoleFilter.current = roleFilter + lastFetchedPage.current = currentPage + if (roleFilter === "all") { + fetchAllUsers() + } else { + const role = roles.find(r => r.roleKey === roleFilter) + if (role) { + fetchUsersByRole(role.roleId, currentPage) + } + } } } } - }, [roles, roleFilter, currentPage]) + }, [roles, roleFilter, currentPage, userRoleFromApi]) // 当选择的角色改变时,重置页码并获取数据 const handleRoleFilterChange = (value: string) => { @@ -389,7 +436,7 @@ export default function CompanyPermissionsPage() {

公司权限管理

管理公司内部用户账户和权限分配

- {isAdmin && ( + {(isAdmin || userRoleFromApi === "common") && ( )} @@ -410,25 +461,33 @@ export default function CompanyPermissionsPage() { {/* Stats Cards */}
- {roles.map((role) => { - const roleInfo = getRoleInfo(role.roleKey) - const RoleIcon = roleInfo.icon - const userCount = groupedUsers[role.roleKey]?.length || 0 + {roles + .filter((role) => { + // 如果用户角色是 common,只显示商场管理员(mall) + if (userRoleFromApi === "common") { + return role.roleKey === "mall" + } + return true + }) + .map((role) => { + const roleInfo = getRoleInfo(role.roleKey) + const RoleIcon = roleInfo.icon + const userCount = groupedUsers[role.roleKey]?.length || 0 - return ( - - -
-
-

{role.roleName}

-

{userCount}

+ return ( + + +
+
+

{role.roleName}

+

{userCount}

+
+
- -
- - - ) - })} + + + ) + })}
{/* Filters and Search */} @@ -456,12 +515,22 @@ export default function CompanyPermissionsPage() { - 全部角色 - {roles.map((role) => ( - - {role.roleName} - - ))} + {userRoleFromApi !== "common" && ( + 全部角色 + )} + {roles + .filter((role) => { + // 如果用户角色是 common,只显示商场管理员(mall) + if (userRoleFromApi === "common") { + return role.roleKey === "mall" + } + return true + }) + .map((role) => ( + + {role.roleName} + + ))} @@ -486,29 +555,39 @@ export default function CompanyPermissionsPage() { {/* Tabs */}
- - {roles.map((role) => ( + {userRoleFromApi !== "common" && ( - ))} + )} + {roles + .filter((role) => { + // 如果用户角色是 common,只显示商场管理员(mall) + if (userRoleFromApi === "common") { + return role.roleKey === "mall" + } + return true + }) + .map((role) => ( + + ))}
{activeTab === "all" ? ( @@ -650,7 +729,15 @@ export default function CompanyPermissionsPage() { ) } -function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void }) { +function CreateUserForm({ + roles, + userRoleFromApi, + onClose +}: { + roles: Role[]; + userRoleFromApi: string; + onClose: () => void +}) { const [formData, setFormData] = useState({ username: "", name: "", @@ -664,6 +751,15 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void const [availableProvinces, setAvailableProvinces] = useState([]) const [provincesLoading, setProvincesLoading] = useState(false) + // 如果当前用户是 common 角色,默认选中商场管理员并加载省份数据 + useEffect(() => { + if (userRoleFromApi === "common") { + setFormData(prev => ({ ...prev, role: "mall" })) + fetchProvinces("mall") + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [userRoleFromApi]) + // 根据角色获取省份数据 const fetchProvinces = async (roleKey: string) => { try { @@ -685,9 +781,37 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void } } + // 用户名校验函数 + const validateUsername = (username: string): string | null => { + if (!username || username.trim() === '') { + return '用户名不能为空' + } + // 检查是否包含空格 + if (username.includes(' ')) { + return '用户名不能包含空格' + } + // 检查是否包含其他非法字符(只允许字母、数字、下划线、中划线) + const validPattern = /^[a-zA-Z0-9_-]+$/ + if (!validPattern.test(username)) { + return '用户名只能包含字母、数字、下划线和中划线' + } + // 检查长度(根据实际需求调整) + if (username.length < 3 || username.length > 20) { + return '用户名长度应在3-20个字符之间' + } + return null + } + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() + // 校验用户名 + const usernameError = validateUsername(formData.username) + if (usernameError) { + alert(usernameError) + return + } + if (!formData.role) { alert('请选择角色') return @@ -751,6 +875,14 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void })) } + // 处理用户名输入,过滤非法字符 + const handleUsernameChange = (e: React.ChangeEvent) => { + let value = e.target.value + // 移除空格和其他非法字符(只保留字母、数字、下划线、中划线) + value = value.replace(/[^a-zA-Z0-9_-]/g, '') + setFormData({ ...formData, username: value }) + } + const shouldShowProvinces = formData.role && formData.role !== "admin" && formData.role !== "merchant" return ( @@ -761,10 +893,14 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void setFormData({ ...formData, username: e.target.value })} - placeholder="请输入用户名" + onChange={handleUsernameChange} + placeholder="请输入用户名(3-20个字符,仅支持字母、数字、下划线、中划线)" required + maxLength={20} /> + {formData.username && formData.username.length > 0 && formData.username.length < 3 && ( +

用户名长度至少3个字符

+ )}
@@ -799,18 +935,30 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void fetchProvinces(value) } }} + disabled={userRoleFromApi === "common"} > - {roles.map((role) => ( - - {role.roleName} - - ))} + {roles + .filter((role) => { + // 如果当前用户是 common 角色,只显示商场管理员 + if (userRoleFromApi === "common") { + return role.roleKey === "mall" + } + return true + }) + .map((role) => ( + + {role.roleName} + + ))} + {userRoleFromApi === "common" && ( +

您只能添加商场管理员

+ )}
{shouldShowProvinces && ( @@ -1039,9 +1187,9 @@ function EditUserForm({ setFormData({ ...formData, username: e.target.value })} - placeholder="请输入用户名" - required + disabled + placeholder="用户名不可修改" + className="bg-gray-100 cursor-not-allowed" />
diff --git a/src/components/pages/EquipmentPage.tsx b/src/components/pages/EquipmentPage.tsx index 625e843..7dbf39e 100644 --- a/src/components/pages/EquipmentPage.tsx +++ b/src/components/pages/EquipmentPage.tsx @@ -165,43 +165,14 @@ export default function EquipmentPage() { } } - // 获取单个商户的设备数量 - const fetchMerchantEquipmentCount = async (merchantId: string) => { - try { - const response = await apiGet(`/back/equipment/merchant/${merchantId}/count`) - if (response.code === 200) { - return response.data - } - return null - } catch (error) { - console.error(`获取商户${merchantId}设备数量失败:`, error) - return null - } - } - - // 获取商户列表 - const fetchMerchants = async () => { + // 获取商户列表(用于添加/编辑设备选择,不获取设备数量) + const fetchMerchantsForSelect = async () => { setLoadingMerchants(true) try { const response = await apiGet('/back/general/provinceMerchants') if (response.code === 200) { const merchantsData = response.data || [] - - // 为每个商户获取设备数量 - const merchantsWithCounts = await Promise.all( - merchantsData.map(async (merchant: any) => { - const equipmentCount = await fetchMerchantEquipmentCount(merchant.id) - return { - ...merchant, - equipmentCount: equipmentCount?.totalCount || 0, - normalCount: equipmentCount?.count || 0, - expiringCount: equipmentCount?.countEnd || 0, - expiredCount: equipmentCount?.countExpire || 0 - } - }) - ) - - setMerchants(merchantsWithCounts) + setMerchants(merchantsData) } } catch (error) { console.error('获取商户列表失败:', error) @@ -361,7 +332,7 @@ export default function EquipmentPage() { setIsEditEquipmentOpen(true) // 加载商户和设备类型数据 if (merchants.length === 0) { - fetchMerchants() + fetchMerchantsForSelect() } if (Object.keys(equipmentTypes).length === 0) { fetchEquipmentTypes() @@ -613,7 +584,7 @@ export default function EquipmentPage() { disabled={loadingMerchants} onOpenChange={(open) => { if (open && merchants.length === 0 && !loadingMerchants) { - fetchMerchants() + fetchMerchantsForSelect() } if (!open) { setMerchantSearchForAdd("") @@ -835,7 +806,7 @@ export default function EquipmentPage() { disabled={loadingMerchants} onOpenChange={(open) => { if (open && merchants.length === 0 && !loadingMerchants) { - fetchMerchants() + fetchMerchantsForSelect() } if (!open) { setMerchantSearchForEdit("") diff --git a/src/components/pages/MerchantsPage.tsx b/src/components/pages/MerchantsPage.tsx index 4b4068e..84efb31 100644 --- a/src/components/pages/MerchantsPage.tsx +++ b/src/components/pages/MerchantsPage.tsx @@ -166,15 +166,25 @@ export default function MerchantsPage() { // 获取商场列表 const fetchMalls = async (province?: string) => { + if (!province) { + setMalls([]) + return + } setLoadingMalls(true) try { const url = `/back/info/list?province=${province}&pageNum=1&pageSize=100` const response = await apiGet(url) if (response.code === 200) { - setMalls(response.rows || []) + const mallsData = response.rows || [] + console.log('获取商场列表成功:', mallsData) + setMalls(mallsData) + } else { + console.error('获取商场列表失败:', response.msg) + setMalls([]) } } catch (error) { console.error('获取商场列表失败:', error) + setMalls([]) } finally { setLoadingMalls(false) } @@ -343,6 +353,47 @@ export default function MerchantsPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [merchantNameSearch, contactPersonSearch, contactPhoneSearch, statusFilter]) + // 当添加商户对话框打开且已选择省份时,自动加载商场列表 + useEffect(() => { + if (isAddMerchantOpen && newMerchant.province && malls.length === 0 && !loadingMalls) { + fetchMalls(newMerchant.province) + } + }, [isAddMerchantOpen, newMerchant.province]) + + // 当编辑商户对话框打开且已选择省份时,自动加载商场列表 + useEffect(() => { + if (isEditMerchantOpen && editMerchant.province && !loadingMalls) { + // 如果商场列表为空,或者当前商场不在列表中,重新加载 + if (malls.length === 0 || (editMerchant.mall && !malls.find(m => m.mallId === editMerchant.mall))) { + fetchMalls(editMerchant.province) + } + } + }, [isEditMerchantOpen, editMerchant.province, editMerchant.mall]) + + // 当商场列表加载完成且编辑对话框打开时,根据 mallId 设置 mallUserId + useEffect(() => { + if (isEditMerchantOpen && editMerchant.mall && malls.length > 0 && !loadingMalls) { + // 如果 mallUserId 为空,尝试根据 mallId 找到对应的 mallUser + if (!editMerchant.mallUserId) { + const foundMall = malls.find(m => + m.mallId === editMerchant.mall || + m.id === editMerchant.mall || + (m.mallId && editMerchant.mall && m.mallId.toString() === editMerchant.mall.toString()) + ) + if (foundMall && foundMall.mallUser) { + console.log('找到匹配的商场:', foundMall) + setEditMerchant(prev => ({ + ...prev, + mallUserId: foundMall.mallUser + })) + } else { + console.log('未找到匹配的商场,mallId:', editMerchant.mall, '商场列表:', malls) + } + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEditMerchantOpen, editMerchant.mall, malls.length, loadingMalls]) + const [newEquipment, setNewEquipment] = useState({ name: "", type: "", @@ -459,6 +510,15 @@ export default function MerchantsPage() { } } + // 将设备类型代码转换为中文 + const getEquipmentTypeName = (type: string) => { + const typeMap: { [key: string]: string } = { + "kitchen_automatic_fire_extinguisher": "厨房自动灭火", + "fire_extinguisher": "动火离人" + } + return typeMap[type] || type || '-' + } + // 打开编辑对话框 const handleEditMerchant = (merchant: any) => { setEditMerchant({ @@ -629,6 +689,7 @@ export default function MerchantsPage() {
setMerchantNameSearch(e.target.value)} @@ -638,6 +699,7 @@ export default function MerchantsPage() {
setContactPersonSearch(e.target.value)} @@ -647,6 +709,7 @@ export default function MerchantsPage() {
setContactPhoneSearch(e.target.value)} @@ -958,7 +1021,7 @@ export default function MerchantsPage() {
- + setNewMerchant({ ...newMerchant, name: e.target.value })} placeholder="请输入商户名称" />
- + setNewMerchant({ ...newMerchant, contact: e.target.value })} placeholder="请输入联系人姓名" />
- + setNewMerchant({ ...newMerchant, phone: e.target.value })} placeholder="请输入联系电话" @@ -1035,8 +1110,8 @@ export default function MerchantsPage() { 点击加载总商户数据 ) : ( - totalMerchants.map((merchant) => ( - + totalMerchants.map((merchant, index) => ( + {merchant.nickName} )) @@ -1216,11 +1291,23 @@ export default function MerchantsPage() { } /> - {malls.map((mall) => ( - - {mall.mallName} + {loadingMalls ? ( + + 加载中... - ))} + ) : malls.length === 0 ? ( + + {!editMerchant.province ? "请先选择省份" : "该省份暂无商场数据"} + + ) : ( + malls + .filter(mall => mall.mallUser) // 过滤掉没有 mallUser 的项 + .map((mall, index) => ( + + {mall.mallName || '未命名商场'} + + )) + )}
@@ -1243,9 +1330,9 @@ export default function MerchantsPage() { />
- + setEditMerchant({ ...editMerchant, phone: e.target.value })} placeholder="请输入联系电话" @@ -1386,16 +1473,16 @@ export default function MerchantsPage() { ) : (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).length > 0 ? ( - (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).map((equipment: any) => ( - + (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).map((equipment: any, index: number) => ( + {equipment.equipmentId} {equipment.equipmentName} - - {equipment.equipmentType} + + {getEquipmentTypeName(equipment.equipmentType)} {equipment.installationDate} @@ -1425,8 +1512,8 @@ export default function MerchantsPage() { {loadingMerchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] ? (
加载中...
) : (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).length > 0 ? ( - (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).map((equipment: any) => ( - + (merchantEquipments[selectedMerchant?.merchantsId || selectedMerchant?.id] || []).map((equipment: any, index: number) => ( + {/* 设备基本信息 */}
@@ -1441,7 +1528,7 @@ export default function MerchantsPage() {
类型
-
{equipment.equipmentType || '-'}
+
{getEquipmentTypeName(equipment.equipmentType)}
安装日期
diff --git a/src/components/pages/WorkOrdersPage.tsx b/src/components/pages/WorkOrdersPage.tsx index 365ecae..62be036 100644 --- a/src/components/pages/WorkOrdersPage.tsx +++ b/src/components/pages/WorkOrdersPage.tsx @@ -718,7 +718,7 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) { return merchants } const lowerSearch = searchTerm.toLowerCase() - return merchants.filter(merchant => + return merchants.filter(merchant => merchant.merchantName.toLowerCase().includes(lowerSearch) || merchant.contactPerson.toLowerCase().includes(lowerSearch) || merchant.contactPhone.includes(lowerSearch) || @@ -1006,7 +1006,7 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) { {availableEquipment.map((equipment) => ( - {equipment.equipmentName} ({equipment.equipmentType}) + {equipment.equipmentName} ))}