diff --git a/src/main/java/com/campus/water/service/AdminService.java b/src/main/java/com/campus/water/service/AdminService.java index e21d723..bfe1837 100644 --- a/src/main/java/com/campus/water/service/AdminService.java +++ b/src/main/java/com/campus/water/service/AdminService.java @@ -69,42 +69,43 @@ public class AdminService { } /** - * 新增/修改管理员(支持指定角色) - * 重写保存方法,增加区域校验(区域管理员必须关联区域) - */ - public Admin saveAdmin(Admin admin) { - admin.setUpdatedTime(LocalDateTime.now()); - if (admin.getCreatedTime() == null) { - admin.setCreatedTime(LocalDateTime.now()); - } + * 新增/修改管理员(支持指定角色) + * 重写保存方法,增加区域校验(区域管理员必须关联区域) + */ +public Admin saveAdmin(Admin admin) { + admin.setUpdatedTime(LocalDateTime.now()); + if (admin.getCreatedTime() == null) { + admin.setCreatedTime(LocalDateTime.now()); + } - // 区域管理员(ROLE_AREA_ADMIN)的专属校验逻辑 - if (admin.getRole() == Admin.AdminRole.ROLE_AREA_ADMIN) { - // 1. 若未填写区域ID(null/空字符串),直接放行(支持先创建管理员,后续补填) - if (admin.getAreaId() == null || admin.getAreaId().trim().isEmpty()) { - admin.setAreaId(null); // 统一置为null,避免空字符串冗余数据 - // 无需校验,直接允许保存 - } else { - // 2. 若填写了区域ID,进行严格校验:区域存在 + 类型为校区(禁止市区) - String areaId = admin.getAreaId().trim(); - // 校验区域是否存在 - Area targetArea = areaRepository.findById(areaId) - .orElseThrow(() -> new RuntimeException("关联的区域不存在:" + areaId)); - // 核心校验:仅允许关联校区,禁止关联市区 - if (Area.AreaType.zone.equals(targetArea.getAreaType())) { - throw new RuntimeException("区域管理员仅允许关联校区,不能关联市区,请重新选择"); - } - // 校验通过,保留填写的合法校区ID - admin.setAreaId(areaId); - } + // 区域管理员(ROLE_AREA_ADMIN)的专属校验逻辑 + if (admin.getRole() == Admin.AdminRole.ROLE_AREA_ADMIN) { + // 1. 若未填写区域ID(null或空字符串),直接放行(支持先创建管理员,后续补填) + if (admin.getAreaId() == null || admin.getAreaId().trim().isEmpty()) { + admin.setAreaId(null); // 统一置为null,避免空字符串冗余数据 + // 无需校验,直接允许保存 } else { - // 非区域管理员,清空区域ID,避免冗余数据 - admin.setAreaId(null); + // 2. 若填写了区域ID,进行严格校验:区域存在 + 类型为校区(禁止市区) + String areaId = admin.getAreaId().trim(); + // 校验区域是否存在 + Area targetArea = areaRepository.findById(areaId) + .orElseThrow(() -> new RuntimeException("关联的区域不存在:" + areaId)); + // 核心校验:仅允许关联校区,禁止关联市区 + if (Area.AreaType.zone.equals(targetArea.getAreaType())) { + throw new RuntimeException("区域管理员仅允许关联校区,不能关联市区,请重新选择"); + } + // 校验通过,保留填写的合法校区ID + admin.setAreaId(areaId); } - - return adminRepository.save(admin); + } else { + // 非区域管理员,清空区域ID,避免冗余数据 + admin.setAreaId(null); } + return adminRepository.save(admin); +} + + /** * 删除管理员 */ diff --git a/src/main/resources/web/src/views/area/Campus.vue b/src/main/resources/web/src/views/area/Campus.vue index bf196da..82fa3f1 100644 --- a/src/main/resources/web/src/views/area/Campus.vue +++ b/src/main/resources/web/src/views/area/Campus.vue @@ -50,6 +50,12 @@ {{ campus.managerPhone }} {{ formatDate(campus.createdTime) }} + + + + + - @@ -971,6 +1018,7 @@ onMounted(async () => { display: flex; gap: 12px; align-items: center; + flex-wrap: wrap; } .search-box { @@ -994,12 +1042,18 @@ onMounted(async () => { cursor: pointer; } +.area-filter { + display: flex; + gap: 8px; +} + .filter-select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; background: white; cursor: pointer; + min-width: 120px; } .equipment-table { @@ -1227,10 +1281,14 @@ onMounted(async () => { width: 100%; } - .search-box, .filter-select { + .search-box, .area-filter, .filter-select { width: 100%; } + .area-filter { + flex-direction: column; + } + .modal-content { width: 90%; min-width: auto; diff --git a/src/main/resources/web/src/views/equipment/WaterSupplier.vue b/src/main/resources/web/src/views/equipment/WaterSupplier.vue index 77fb97f..9a5627e 100644 --- a/src/main/resources/web/src/views/equipment/WaterSupplier.vue +++ b/src/main/resources/web/src/views/equipment/WaterSupplier.vue @@ -22,18 +22,39 @@ - - + +
+ + + +
- - - - +
@@ -172,10 +196,13 @@
@@ -243,16 +270,32 @@ interface DeviceDetail { parentMakerId?: string } +// 区域类型定义 +interface Area { + areaId: string + areaName: string + areaType: string + parentAreaId: string | null + address: string + manager: string + managerPhone: string +} + // 响应式数据 const devices = ref([]) const searchKeyword = ref('') -const selectedArea = ref('') // 片区筛选值 +const selectedCity = ref('') // 市区筛选值 +const selectedCampus = ref('') // 校区筛选值 const selectedStatus = ref('') // 状态筛选值 const currentPage = ref(1) const pageSize = 10 // 每页显示数量 const router = useRouter() const authStore = useAuthStore() +// 片区相关数据 +const cityList = ref([]) // 市区列表 +const campusList = ref([]) // 校区列表 + // 新增:添加设备相关状态 const showAddModal = ref(false) const newDevice = ref({ @@ -283,6 +326,103 @@ const availableMakersForEdit = ref<{id: string, name: string}[]>([]) const showDeleteModal = ref(false) const currentDeviceId = ref('') +// 加载市区列表 +const loadCityList = async (): Promise => { + try { + const token = authStore.token + if (!token) { + console.warn('未获取到 Token,跳转到登录页') + await router.push('/login') + return + } + + console.log('开始加载市区列表...') + + const result = await request('/api/web/area/cities', { method: 'GET' }) + + if (result && typeof result === 'object' && 'code' in result) { + if (result.code === 200 && result.data && Array.isArray(result.data)) { + cityList.value = result.data + console.log(`获取到${cityList.value.length}个市区`) + } else { + console.warn('API响应非成功状态或数据格式错误:', result) + cityList.value = [] + } + } else if (Array.isArray(result)) { + cityList.value = result + } else { + console.warn('API响应数据格式错误:', result) + cityList.value = [] + } + } catch (error) { + console.error('加载市区列表失败:', error) + cityList.value = [] + if ((error as Error).message.includes('401')) { + authStore.logout() + await router.push('/login') + } + } +} + +// 根据市区ID加载校区列表 +const loadCampusListByCity = async (cityId: string): Promise => { + try { + const token = authStore.token + if (!token) { + console.warn('未获取到 Token,跳转到登录页') + await router.push('/login') + return + } + + console.log(`开始加载市区 ${cityId} 的校区列表...`) + + const result = await request(`/api/web/area/campuses/${cityId}`, { method: 'GET' }) + + if (result && typeof result === 'object' && 'code' in result) { + if (result.code === 200 && result.data && Array.isArray(result.data)) { + campusList.value = result.data + console.log(`获取到${campusList.value.length}个校区`) + } else { + console.warn('API响应非成功状态或数据格式错误:', result) + campusList.value = [] + } + } else if (Array.isArray(result)) { + campusList.value = result + } else { + console.warn('API响应数据格式错误:', result) + campusList.value = [] + } + } catch (error) { + console.error('加载校区列表失败:', error) + campusList.value = [] + if ((error as Error).message.includes('401')) { + authStore.logout() + await router.push('/login') + } + } +} + +// 市区选择变化时的处理 +const onCityChange = async () => { + // 清空校区选择 + selectedCampus.value = '' + campusList.value = [] + + if (selectedCity.value) { + await loadCampusListByCity(selectedCity.value) + } else { + // 如果清空市区选择,也清空校区列表 + campusList.value = [] + } +} + +// 校区选择变化时的处理 +const onCampusChange = () => { + // 选择校区后直接触发设备加载 + currentPage.value = 1 + loadWaterSuppliers() +} + // 加载供水机列表 const loadWaterSuppliers = async () => { try { @@ -299,8 +439,9 @@ const loadWaterSuppliers = async () => { if (selectedStatus.value && selectedStatus.value !== '') { params.append('status', selectedStatus.value); } - if (selectedArea.value && selectedArea.value !== '') { - params.append('areaId', selectedArea.value); + // 只有在选择了校区时才按校区筛选,市区选择不作为筛选条件 + if (selectedCampus.value && selectedCampus.value !== '') { + params.append('areaId', selectedCampus.value) } params.append('deviceType', 'water_supply'); // 供水机类型 @@ -421,13 +562,21 @@ const loadAvailableMakersForEdit = async () => { } // 多条件过滤设备数据 +// 修改 filteredDevices 的计算属性,只根据校区进行筛选 const filteredDevices = computed(() => { return devices.value.filter(device => { const keywordMatch = searchKeyword.value.trim() === '' || device.id.toLowerCase().includes(searchKeyword.value.toLowerCase()) || device.location.toLowerCase().includes(searchKeyword.value.toLowerCase()) - const areaMatch = selectedArea.value === '' || device.area === selectedArea.value + // 只根据校区进行筛选,如果校区未选择则不过滤片区 + let areaMatch = true + if (selectedCampus.value && selectedCampus.value !== '') { + // 只有在选择了校区时才进行片区匹配 + areaMatch = device.area === selectedCampus.value + } + // 如果没有选择校区,则不进行片区过滤 + const statusMatch = selectedStatus.value === '' || device.status === selectedStatus.value return keywordMatch && areaMatch && statusMatch @@ -646,7 +795,8 @@ const deleteDevice = async () => { } // 页面加载时获取数据 -onMounted(() => { +onMounted(async () => { + await loadCityList() // 加载市区列表 loadWaterSuppliers() }) @@ -701,6 +851,7 @@ onMounted(() => { display: flex; gap: 12px; align-items: center; + flex-wrap: wrap; } .search-box { @@ -724,12 +875,18 @@ onMounted(() => { cursor: pointer; } +.area-filter { + display: flex; + gap: 8px; +} + .filter-select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; background: white; cursor: pointer; + min-width: 120px; } .equipment-table { @@ -957,14 +1114,17 @@ onMounted(() => { width: 100%; } - .search-box, .filter-select { + .search-box, .area-filter, .filter-select { width: 100%; } + .area-filter { + flex-direction: column; + } + .modal-content { width: 90%; min-width: auto; } } - diff --git a/src/main/resources/web/src/views/personnel/Admin.vue b/src/main/resources/web/src/views/personnel/Admin.vue index 400fb85..bc8d0fe 100644 --- a/src/main/resources/web/src/views/personnel/Admin.vue +++ b/src/main/resources/web/src/views/personnel/Admin.vue @@ -1,4 +1,3 @@ -