待审核与人工派单 #112

Merged
pc8xi2fbj merged 3 commits from zhanghongwei_branch into develop 2 weeks ago

@ -73,15 +73,20 @@ public class WorkOrderController {
}
}
// 新增:审核工单接口(管理员专用)
// 1. 创建请求体类
@Data
public static class ReviewOrderRequest {
private String orderId;
private boolean approved;
}
// 2. 修改接口接收方式
@PostMapping("/review")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
public ResultVO<Boolean> reviewOrder(
@RequestParam String orderId,
@RequestParam boolean approved) {
public ResultVO<Boolean> reviewOrder(@RequestBody ReviewOrderRequest request) {
try {
boolean result = workOrderService.reviewOrder(orderId, approved);
return result ? ResultVO.success(true, approved ? "审核通过" : "审核不通过")
boolean result = workOrderService.reviewOrder(request.getOrderId(), request.isApproved());
return result ? ResultVO.success(true, request.isApproved() ? "审核通过" : "审核不通过")
: ResultVO.error(400, "审核失败,工单状态异常");
} catch (Exception e) {
return ResultVO.error(500, "审核失败:" + e.getMessage());

@ -72,7 +72,7 @@ public class AreaController {
@RequestBody @Parameter(description = "区域信息areaId为必填") Area area
) {
try {
if (area.getAreaId() == null || area.getAreaId().trim().isEmpty()) {
if (area.getAreaId() == null) {
return ResponseEntity.ok(ResultVO.error(400, "区域ID不能为空"));
}
Area updatedArea = areaService.updateArea(area);

@ -1,4 +1,4 @@
<!-- src/views/area/CampusManagement.vue -->
<!-- src/views/area/Campus.vue -->
<template>
<div class="campus-page">
<!-- 页面标题和面包屑 -->
@ -11,48 +11,49 @@
<div class="action-bar">
<!-- 新增校区按钮 -->
<button class="btn-add" @click="handleAddCampus"></button>
<!-- 校区下拉筛选 -->
<div class="filter-box">
<label>选择校区</label>
<select v-model="selectedCampus" @change="handleCampusChange" class="campus-select">
<option value="">全部校区</option>
<option v-for="campus in campusList" :key="campus.id" :value="campus.id">
{{ campus.name }}
</option>
</select>
</div>
<!-- 导航到片区管理按钮 -->
<button class="btn-nav" @click="goToZoneManagement"></button>
</div>
<!-- 校区表格新增所属市区列 -->
<!-- 校区表格 -->
<div class="card">
<table class="campus-table">
<thead>
<tr>
<th>校区</th>
<th>所属市区</th> <!-- 新增列 -->
<th>设备数量</th>
<th>范围</th>
<th>校区名称</th>
<th>地址</th>
<th>负责人</th>
<th>联系电话</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="campus in filteredCampus" :key="campus.id">
<td>{{ campus.name }}</td>
<td>{{ getAreaName(campus.areaId) }}</td> <!-- 显示所属市区名称 -->
<td>{{ campus.deviceCount }}</td>
<td>{{ campus.range }}</td>
<tr v-for="campus in filteredCampus" :key="campus.areaId">
<td>{{ campus.areaName }}</td>
<td>{{ campus.address }}</td>
<td>{{ campus.manager }}</td>
<td>{{ campus.managerPhone }}</td>
<td>{{ formatDate(campus.createdTime) }}</td>
<td class="operation-buttons">
<button
class="btn-delete"
@click="handleDelete(campus.id)"
<button
class="btn-edit"
@click="handleEdit(campus)"
>
编辑
</button>
<button
class="btn-delete"
@click="handleDelete(campus.areaId, campus.areaName)"
>
删除
</button>
</td>
</tr>
<tr v-if="filteredCampus.length === 0">
<td colspan="5" class="no-data">暂无校区数据</td> <!-- 列数调整为5 -->
<td colspan="6" class="no-data">
{{ loading ? '正在加载数据...' : '暂无校区数据' }}
</td>
</tr>
</tbody>
</table>
@ -60,9 +61,9 @@
<!-- 分页控件 -->
<div class="pagination">
<button
class="page-btn"
:disabled="currentPage === 1"
<button
class="page-btn"
:disabled="currentPage === 1 || loading"
@click="currentPage--"
>
上一页
@ -70,16 +71,16 @@
<span class="page-info">
{{ currentPage }} / {{ totalPages }}
</span>
<button
class="page-btn"
:disabled="currentPage === totalPages"
<button
class="page-btn"
:disabled="currentPage === totalPages || loading"
@click="currentPage++"
>
下一页
</button>
</div>
<!-- 新增/编辑校区弹窗新增所属市区选择 -->
<!-- 新增/编辑校区弹窗 -->
<div class="modal-mask" v-if="showModal">
<div class="modal-container">
<div class="modal-header">
@ -90,48 +91,45 @@
<form @submit.prevent="handleSave">
<div class="form-item">
<label>校区名称</label>
<input
type="text"
v-model="formData.name"
<input
type="text"
v-model="formData.areaName"
placeholder="请输入校区名称"
required
>
</div>
<div class="form-item">
<label>所属市区</label> <!-- 新增表单项 -->
<select
v-model="formData.areaId"
class="area-select"
<label>地址</label>
<input
type="text"
v-model="formData.address"
placeholder="请输入校区地址"
required
>
<option value="">请选择所属市区</option>
<option v-for="area in areaList" :key="area.id" :value="area.id">
{{ area.name }}
</option>
</select>
</div>
<div class="form-item">
<label>校区范围</label>
<textarea
v-model="formData.range"
placeholder="请输入校区范围描述"
rows="3"
<label>负责人</label>
<input
type="text"
v-model="formData.manager"
placeholder="请输入负责人姓名"
required
></textarea>
>
</div>
<div class="form-item">
<label>设备数量</label>
<input
type="number"
v-model="formData.deviceCount"
placeholder="请输入设备数量"
min="0"
<label>联系电话</label>
<input
type="text"
v-model="formData.managerPhone"
placeholder="请输入负责人联系电话"
required
>
</div>
<div class="form-actions">
<button type="button" class="btn-cancel" @click="showModal = false">取消</button>
<button type="submit" class="btn-submit">保存</button>
<button type="submit" class="btn-submit" :disabled="saving">
{{ saving ? '保存中...' : '保存' }}
</button>
</div>
</form>
</div>
@ -149,7 +147,9 @@
<p>确定要删除 "{{ deleteCampusName }}" 校区吗此操作不可撤销</p>
<div class="form-actions">
<button type="button" class="btn-cancel" @click="showDeleteConfirm = false">取消</button>
<button type="button" class="btn-delete-confirm" @click="confirmDelete"></button>
<button type="button" class="btn-delete-confirm" @click="confirmDelete" :disabled="deleting">
{{ deleting ? '删除中...' : '确认删除' }}
</button>
</div>
</div>
</div>
@ -158,67 +158,30 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { request } from '@/api/request' //
import { useAuthStore } from '@/stores/auth' // authStore
//
//
const router = useRouter()
const authStore = useAuthStore()
// Area
interface Area {
id: string
name: string
deviceCount: number
range: string
}
// areaId
interface Campus {
id: string
name: string
areaId: string // ID
deviceCount: number
range: string
}
//
const areaList = ref<Area[]>([
{
id: '1',
name: '市区东区',
deviceCount: 28,
range: '东至东风路,西至解放路,南至人民路,北至建设路'
},
{
id: '2',
name: '市区西区',
deviceCount: 19,
range: '东至解放路,西至滨河路,南至青年路,北至黄河路'
}
])
//
const campusList = ref<Campus[]>([
{
id: '1',
name: '主校区',
areaId: '1', //
deviceCount: 89,
range: '包含教学区、生活区、运动区,覆盖整个主校区范围'
},
{
id: '2',
name: '分校区',
areaId: '2', // 西
deviceCount: 45,
range: '包含新教学楼、实训楼、学生公寓1-5号楼'
},
{
id: '3',
name: '南校区',
areaId: '1', //
deviceCount: 67,
range: '包含研究生院、国际交流中心、留学生公寓'
}
])
areaId: string
areaName: string
areaType: 'campus' | 'building' | 'zone'
parentAreaId: string | null
address: string
manager: string
managerPhone: string
createdTime?: Date
updatedTime?: Date
}
//
const campusList = ref<Area[]>([])
const selectedCampus = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
@ -227,31 +190,39 @@ const isEdit = ref(false)
const showDeleteConfirm = ref(false)
const deleteCampusId = ref('')
const deleteCampusName = ref('')
const loading = ref(false) //
const saving = ref(false) //
const deleting = ref(false) //
// areaId
const formData = ref<Campus>({
id: '',
name: '',
//
const formData = ref<Area>({
areaId: '',
deviceCount: 0,
range: ''
areaName: '',
areaType: 'campus',
parentAreaId: null,
address: '',
manager: '',
managerPhone: '',
createdTime: undefined,
updatedTime: undefined
})
// ID
const getAreaName = (areaId: string) => {
const area = areaList.value.find(item => item.id === areaId)
return area ? area.name : '未指定'
//
const formatDate = (date: Date | undefined) => {
if (!date) return '-'
const d = new Date(date)
return d.toLocaleDateString('zh-CN')
}
//
const filteredCampus = computed(() => {
let filtered = [...campusList.value]
//
if (selectedCampus.value) {
filtered = filtered.filter(campus => campus.id === selectedCampus.value)
filtered = filtered.filter(campus => campus.areaId === selectedCampus.value)
}
//
const startIndex = (currentPage.value - 1) * pageSize.value
const endIndex = startIndex + pageSize.value
@ -260,15 +231,49 @@ const filteredCampus = computed(() => {
//
const totalPages = computed(() => {
const filteredCount = selectedCampus.value
? campusList.value.filter(campus => campus.id === selectedCampus.value).length
const filteredCount = selectedCampus.value
? campusList.value.filter(campus => campus.areaId === selectedCampus.value).length
: campusList.value.length
return Math.ceil(filteredCount / pageSize.value)
})
//
const handleCampusChange = () => {
currentPage.value = 1 //
//
const fetchCampusList = async () => {
loading.value = true
try {
const token = authStore.token
if (!token) {
console.warn('未获取到 Token跳转到登录页')
router.push('/login')
return
}
const response = await request<{
code: number
msg: string
data: Area[]
}>('/api/web/area/list-campus', {
method: 'GET',
})
if (response.code === 200) {
campusList.value = response.data
} else {
const errorMsg = response.msg || `获取失败(错误码:${response.code}`
console.error('获取校区列表失败:', errorMsg)
alert(`获取校区列表失败:${errorMsg}`)
}
} catch (error: any) {
console.error('请求异常:', error)
const errorMsg = error.message.includes('401') || error.message.includes('403')
? '权限不足或登录已过期,请重新登录'
: error.message.includes('Network')
? '网络连接失败,请检查网络'
: error.message || '获取数据失败,请稍后重试'
alert(`获取校区列表失败:${errorMsg}`)
} finally {
loading.value = false
}
}
//
@ -276,57 +281,160 @@ const handleAddCampus = () => {
isEdit.value = false
//
formData.value = {
id: '',
name: '',
areaId: '',
deviceCount: 0,
range: ''
areaName: '',
areaType: 'campus',
parentAreaId: null,
address: '',
manager: '',
managerPhone: '',
createdTime: undefined,
updatedTime: undefined
}
showModal.value = true
}
//
const handleEdit = (campus: Area) => {
isEdit.value = true
formData.value = { ...campus }
showModal.value = true
}
//
const handleDelete = (id: string) => {
const campus = campusList.value.find(item => item.id === id)
if (campus) {
deleteCampusId.value = id
deleteCampusName.value = campus.name
showDeleteConfirm.value = true
}
const handleDelete = (id: string, name: string) => {
deleteCampusId.value = id
deleteCampusName.value = name
showDeleteConfirm.value = true
}
//
const confirmDelete = () => {
campusList.value = campusList.value.filter(campus => campus.id !== deleteCampusId.value)
showDeleteConfirm.value = false
//
if (selectedCampus.value === deleteCampusId.value) {
selectedCampus.value = ''
const confirmDelete = async () => {
deleting.value = true
try {
const token = authStore.token
if (!token) {
console.warn('未获取到 Token跳转到登录页')
router.push('/login')
return
}
const response = await request<{
code: number
msg: string
}>(`/api/web/area/delete/${deleteCampusId.value}`, {
method: 'DELETE',
})
if (response.code === 200) {
fetchCampusList() //
showDeleteConfirm.value = false
} else {
const errorMsg = response.msg || `删除失败(错误码:${response.code}`
console.error('删除校区失败:', errorMsg)
alert(`删除校区失败:${errorMsg}`)
}
} catch (error: any) {
console.error('删除请求异常:', error)
const errorMsg = error.message.includes('401') || error.message.includes('403')
? '权限不足或登录已过期,请重新登录'
: error.message.includes('Network')
? '网络连接失败,请检查网络'
: error.message || '删除失败,请稍后重试'
alert(`删除校区失败:${errorMsg}`)
} finally {
deleting.value = false
}
}
//
const handleSave = () => {
if (isEdit.value) {
//
const index = campusList.value.findIndex(item => item.id === formData.value.id)
if (index !== -1) {
campusList.value[index] = { ...formData.value }
const handleSave = async () => {
saving.value = true
try {
const token = authStore.token
if (!token) {
console.warn('未获取到 Token跳转到登录页')
router.push('/login')
return
}
} else {
//
const newCampus: Campus = {
...formData.value,
id: Date.now().toString() // ID
let response
let result
if (isEdit.value) {
//
response = await request<{
code: number
msg: string
data: Area
}>('/api/web/area/update', {
method: 'PUT',
body: JSON.stringify(formData.value)
})
if (response.code === 200) {
fetchCampusList() //
showModal.value = false
} else {
const errorMsg = response.msg || `更新失败(错误码:${response.code}`
console.error('更新校区失败:', errorMsg)
alert(`更新校区失败:${errorMsg}`)
}
} else {
//
const newCampus = {
areaName: formData.value.areaName,
areaType: 'campus' as const,
address: formData.value.address,
manager: formData.value.manager,
managerPhone: formData.value.managerPhone
}
response = await request<{
code: number
msg: string
data: Area
}>('/api/web/area/add', {
method: 'POST',
body: JSON.stringify(newCampus)
})
if (response.code === 200) {
fetchCampusList() //
showModal.value = false
} else {
const errorMsg = response.msg || `新增失败(错误码:${response.code}`
console.error('新增校区失败:', errorMsg)
alert(`新增校区失败:${errorMsg}`)
}
}
campusList.value.unshift(newCampus)
} catch (error: any) {
console.error('保存请求异常:', error)
const errorMsg = error.message.includes('401') || error.message.includes('403')
? '权限不足或登录已过期,请重新登录'
: error.message.includes('Network')
? '网络连接失败,请检查网络'
: error.message || '保存失败,请稍后重试'
alert(`保存校区失败:${errorMsg}`)
} finally {
saving.value = false
}
showModal.value = false
}
//
const goToZoneManagement = () => {
router.push('/area/zone')
}
//
onMounted(() => {
console.log('Token:', authStore.token)
fetchCampusList()
})
</script>
<style scoped>
/* 基础样式 - 与市区管理页面完全一致 */
/* 基础样式 */
.campus-page {
padding: 20px;
}
@ -371,19 +479,20 @@ const handleSave = () => {
background: #359e75;
}
.filter-box {
display: flex;
align-items: center;
gap: 8px;
color: #666;
}
.campus-select, .area-select { /* 新增area-select样式 */
padding: 8px 12px;
border: 1px solid #ddd;
/* 新增导航按钮样式 */
.btn-nav {
background: #1890ff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
min-width: 200px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.btn-nav:hover {
background: #096dd9;
}
/* 表格样式 */
@ -415,6 +524,21 @@ const handleSave = () => {
gap: 8px;
}
.btn-edit {
background-color: #e6f4ff;
color: #1890ff;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
border: none;
transition: opacity 0.3s;
}
.btn-edit:hover {
opacity: 0.9;
}
.btn-delete {
background-color: #ffebe6;
color: #cf1322;
@ -534,7 +658,7 @@ const handleSave = () => {
.form-item input,
.form-item textarea,
.form-item select { /* 新增select样式 */
.form-item select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
@ -570,6 +694,11 @@ const handleSave = () => {
font-size: 14px;
}
.btn-submit:disabled {
background: #a0d911;
cursor: not-allowed;
}
.btn-delete-confirm {
background: #cf1322;
color: white;
@ -580,19 +709,24 @@ const handleSave = () => {
font-size: 14px;
}
.btn-delete-confirm:disabled {
background: #ff7875;
cursor: not-allowed;
}
/* 响应式调整 */
@media (max-width: 768px) {
.action-bar {
flex-direction: column;
align-items: flex-start;
}
.filter-box {
width: 100%;
}
.campus-select, .area-select {
width: 100%;
}
}
</style>
</style>

@ -1,4 +1,3 @@
<!-- src/views/area/AreaManagement.vue -->
<template>
<div class="area-page">
<!-- 页面标题和面包屑 -->
@ -11,15 +10,14 @@
<div class="action-bar">
<!-- 新增片区按钮 -->
<button class="btn-add" @click="handleAddArea"></button>
<!-- 片区下拉筛选 -->
<div class="filter-box">
<label>选择片区</label>
<select v-model="selectedArea" @change="handleAreaChange" class="area-select">
<option value="">全部片区</option>
<!-- 核心修改移除下拉选项中的设备数量展示仅保留片区名称 -->
<option v-for="area in areaList" :key="area.id" :value="area.id">
{{ area.name }}
<option v-for="area in areaList" :key="area.areaId" :value="area.areaId">
{{ area.areaName }}
</option>
</select>
</div>
@ -37,20 +35,29 @@
</tr>
</thead>
<tbody>
<tr v-for="area in filteredAreas" :key="area.id">
<td>{{ area.name }}</td>
<td>{{ area.deviceCount }}</td>
<td>{{ area.range }}</td>
<tr v-for="area in filteredAreas" :key="area.areaId">
<td>{{ area.areaName }}</td>
<td>{{ area.deviceCount || 0 }}</td>
<td>{{ area.address || '未设置' }}</td>
<td class="operation-buttons">
<button
class="btn-delete"
@click="handleDelete(area.id)"
<button
class="btn-edit"
@click="handleEdit(area)"
>
编辑
</button>
<button
class="btn-delete"
@click="handleDelete(area.areaId, area.areaName)"
>
删除
</button>
</td>
</tr>
<tr v-if="filteredAreas.length === 0">
<tr v-if="loading">
<td colspan="4" class="no-data">正在加载数据...</td>
</tr>
<tr v-else-if="filteredAreas.length === 0">
<td colspan="4" class="no-data">暂无片区数据</td>
</tr>
</tbody>
@ -59,9 +66,9 @@
<!-- 分页控件 -->
<div class="pagination">
<button
class="page-btn"
:disabled="currentPage === 1"
<button
class="page-btn"
:disabled="currentPage === 1 || loading"
@click="currentPage--"
>
上一页
@ -69,9 +76,9 @@
<span class="page-info">
{{ currentPage }} / {{ totalPages }}
</span>
<button
class="page-btn"
:disabled="currentPage === totalPages"
<button
class="page-btn"
:disabled="currentPage === totalPages || loading"
@click="currentPage++"
>
下一页
@ -89,35 +96,42 @@
<form @submit.prevent="handleSave">
<div class="form-item">
<label>片区名称</label>
<input
type="text"
v-model="formData.name"
<input
type="text"
v-model="formData.areaName"
placeholder="请输入片区名称"
required
>
</div>
<div class="form-item">
<label>片区范围</label>
<textarea
v-model="formData.range"
<textarea
v-model="formData.address"
placeholder="请输入片区范围描述"
rows="3"
required
></textarea>
</div>
<div class="form-item">
<label>设备数量</label>
<input
type="number"
v-model="formData.deviceCount"
placeholder="请输入设备数量"
min="0"
required
<label>负责人</label>
<input
type="text"
v-model="formData.manager"
placeholder="请输入负责人姓名"
>
</div>
<div class="form-item">
<label>联系电话</label>
<input
type="text"
v-model="formData.managerPhone"
placeholder="请输入负责人联系电话"
>
</div>
<div class="form-actions">
<button type="button" class="btn-cancel" @click="showModal = false">取消</button>
<button type="submit" class="btn-submit">保存</button>
<button type="submit" class="btn-submit" :disabled="saving">
{{ saving ? '保存中...' : '保存' }}
</button>
</div>
</form>
</div>
@ -135,7 +149,9 @@
<p>确定要删除 "{{ deleteAreaName }}" 片区吗此操作不可撤销</p>
<div class="form-actions">
<button type="button" class="btn-cancel" @click="showDeleteConfirm = false">取消</button>
<button type="button" class="btn-delete-confirm" @click="confirmDelete"></button>
<button type="button" class="btn-delete-confirm" @click="confirmDelete" :disabled="deleting">
{{ deleting ? '删除中...' : '确认删除' }}
</button>
</div>
</div>
</div>
@ -144,45 +160,31 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { request } from '@/api/request' //
import { useAuthStore } from '@/stores/auth' // authStore
//
const router = useRouter()
const authStore = useAuthStore()
//
// Area
interface Area {
id: string
name: string
deviceCount: number
range: string
}
//
const areaList = ref<Area[]>([
{
id: '1',
name: '市区东区',
deviceCount: 28,
range: '东至东风路,西至解放路,南至人民路,北至建设路'
},
{
id: '2',
name: '市区西区',
deviceCount: 19,
range: '东至解放路,西至滨河路,南至青年路,北至黄河路'
},
{
id: '3',
name: '校区南区',
deviceCount: 45,
range: '大学校园内南部区域包含教学楼、图书馆、学生宿舍1-8号楼'
},
{
id: '4',
name: '校区北区',
deviceCount: 32,
range: '大学校园内北部区域包含实验楼、体育馆、学生宿舍9-16号楼'
}
])
areaId: string
areaName: string
areaType: 'campus' | 'building' | 'zone'
parentAreaId: string | null
address: string
manager: string
managerPhone: string
deviceCount?: number
createdTime?: Date
updatedTime?: Date
}
//
const areaList = ref<Area[]>([])
const selectedArea = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
@ -191,24 +193,31 @@ const isEdit = ref(false)
const showDeleteConfirm = ref(false)
const deleteAreaId = ref('')
const deleteAreaName = ref('')
const loading = ref(false) //
const saving = ref(false) //
const deleting = ref(false) //
//
const formData = ref<Area>({
id: '',
name: '',
deviceCount: 0,
range: ''
const formData = ref<Partial<Area>>({
areaId: '',
areaName: '',
areaType: 'zone',
parentAreaId: null,
address: '',
manager: '',
managerPhone: '',
deviceCount: 0
})
//
const filteredAreas = computed(() => {
let filtered = [...areaList.value]
//
if (selectedArea.value) {
filtered = filtered.filter(area => area.id === selectedArea.value)
filtered = filtered.filter(area => area.areaId === selectedArea.value)
}
//
const startIndex = (currentPage.value - 1) * pageSize.value
const endIndex = startIndex + pageSize.value
@ -217,12 +226,55 @@ const filteredAreas = computed(() => {
//
const totalPages = computed(() => {
const filteredCount = selectedArea.value
? areaList.value.filter(area => area.id === selectedArea.value).length
const filteredCount = selectedArea.value
? areaList.value.filter(area => area.areaId === selectedArea.value).length
: areaList.value.length
return Math.ceil(filteredCount / pageSize.value)
})
//
//
const fetchAreaList = async () => {
loading.value = true
try {
const token = authStore.token
if (!token) {
console.warn('未获取到 Token跳转到登录页')
router.push('/login')
return
}
// URL
const url = `/api/web/area/list?areaType=zone`
const response = await request<{
code: number
msg: string
data: Area[]
}>(url, {
method: 'GET',
})
if (response.code === 200) {
areaList.value = response.data
} else {
const errorMsg = response.msg || `获取失败(错误码:${response.code}`
console.error('获取片区列表失败:', errorMsg)
alert(`获取片区列表失败:${errorMsg}`)
}
} catch (error: any) {
console.error('请求异常:', error)
const errorMsg = error.message.includes('401') || error.message.includes('403')
? '权限不足或登录已过期,请重新登录'
: error.message.includes('Network')
? '网络连接失败,请检查网络'
: error.message || '获取数据失败,请稍后重试'
alert(`获取片区列表失败:${errorMsg}`)
} finally {
loading.value = false
}
}
//
const handleAreaChange = () => {
currentPage.value = 1 //
@ -233,56 +285,159 @@ const handleAddArea = () => {
isEdit.value = false
//
formData.value = {
id: '',
name: '',
deviceCount: 0,
range: ''
areaId: '',
areaName: '',
areaType: 'zone',
parentAreaId: null,
address: '',
manager: '',
managerPhone: '',
deviceCount: 0
}
showModal.value = true
}
//
const handleEdit = (area: Area) => {
isEdit.value = true
formData.value = { ...area }
showModal.value = true
}
//
const handleDelete = (id: string) => {
const area = areaList.value.find(item => item.id === id)
const handleDelete = (id: string, name: string) => {
const area = areaList.value.find(item => item.areaId === id)
if (area) {
deleteAreaId.value = id
deleteAreaName.value = area.name
deleteAreaName.value = name
showDeleteConfirm.value = true
}
}
//
const confirmDelete = () => {
areaList.value = areaList.value.filter(area => area.id !== deleteAreaId.value)
showDeleteConfirm.value = false
//
if (selectedArea.value === deleteAreaId.value) {
selectedArea.value = ''
const confirmDelete = async () => {
deleting.value = true
try {
const token = authStore.token
if (!token) {
console.warn('未获取到 Token跳转到登录页')
router.push('/login')
return
}
const response = await request<{
code: number
msg: string
}>(`/api/web/area/delete/${deleteAreaId.value}`, {
method: 'DELETE',
})
if (response.code === 200) {
fetchAreaList() //
showDeleteConfirm.value = false
} else {
const errorMsg = response.msg || `删除失败(错误码:${response.code}`
console.error('删除片区失败:', errorMsg)
alert(`删除片区失败:${errorMsg}`)
}
} catch (error: any) {
console.error('删除请求异常:', error)
const errorMsg = error.message.includes('401') || error.message.includes('403')
? '权限不足或登录已过期,请重新登录'
: error.message.includes('Network')
? '网络连接失败,请检查网络'
: error.message || '删除失败,请稍后重试'
alert(`删除片区失败:${errorMsg}`)
} finally {
deleting.value = false
}
}
//
const handleSave = () => {
if (isEdit.value) {
//
const index = areaList.value.findIndex(item => item.id === formData.value.id)
if (index !== -1) {
areaList.value[index] = { ...formData.value }
// handleSave
const handleSave = async () => {
saving.value = true
try {
const token = authStore.token
if (!token) {
console.warn('未获取到 Token跳转到登录页')
router.push('/login')
return
}
} else {
//
const newArea: Area = {
...formData.value,
id: Date.now().toString() // ID
let response
if (isEdit.value) {
// -
response = await request<{
code: number
msg: string
data: Area
}>('/api/web/area/update', {
method: 'PUT',
body: JSON.stringify(formData.value)
})
if (response.code === 200) {
fetchAreaList() //
showModal.value = false
} else {
const errorMsg = response.msg || `更新失败(错误码:${response.code}`
console.error('更新片区失败:', errorMsg)
alert(`更新片区失败:${errorMsg}`)
}
} else {
// - parentAreaId
const newArea = {
areaName: formData.value.areaName,
areaType: 'zone' as const,
address: formData.value.address,
manager: formData.value.manager,
managerPhone: formData.value.managerPhone
}
response = await request<{
code: number
msg: string
data: Area
}>('/api/web/area/add', {
method: 'POST',
body: JSON.stringify(newArea)
})
if (response.code === 200) {
fetchAreaList() //
showModal.value = false
} else {
const errorMsg = response.msg || `新增失败(错误码:${response.code}`
console.error('新增片区失败:', errorMsg)
alert(`新增片区失败:${errorMsg}`)
}
}
areaList.value.unshift(newArea)
} catch (error: any) {
console.error('保存请求异常:', error)
const errorMsg = error.message.includes('401') || error.message.includes('403')
? '权限不足或登录已过期,请重新登录'
: error.message.includes('Network')
? '网络连接失败,请检查网络'
: error.message || '保存失败,请稍后重试'
alert(`保存片区失败:${errorMsg}`)
} finally {
saving.value = false
}
showModal.value = false
}
//
onMounted(() => {
console.log('Token:', authStore.token)
fetchAreaList()
})
</script>
<!-- Urban.vue 文件中确保有正确的样式定义 -->
<style scoped>
/* 基础样式 - 与人员管理页面保持一致 */
/* 确保样式规则完整且正确 */
.area-page {
padding: 20px;
}
@ -371,6 +526,21 @@ const handleSave = () => {
gap: 8px;
}
.btn-edit {
background-color: #e6f4ff;
color: #1890ff;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
border: none;
transition: opacity 0.3s;
}
.btn-edit:hover {
opacity: 0.9;
}
.btn-delete {
background-color: #ffebe6;
color: #cf1322;
@ -541,13 +711,13 @@ const handleSave = () => {
flex-direction: column;
align-items: flex-start;
}
.filter-box {
width: 100%;
}
.area-select {
width: 100%;
}
}
</style>
</style>

@ -145,8 +145,8 @@
<div class="form-group">
<label>所属片区:</label>
<select v-model="newDevice.areaId" required>
<option value="市区">市区</option>
<option value="校区">校区</option>
<option value="A区">A</option>
<option value="B区">B</option>
</select>
</div>
<div class="form-group">

@ -287,7 +287,7 @@ const fetchAdminList = async () => {
admins.value = (response.data || []).map((admin: any) => ({
adminId: admin.adminId || '',
name: admin.adminName || '未知姓名', //
account: admin.account || '',
account: admin.adminId || '',
phone: admin.phone || '未知电话',
role: admin.role || '未知角色',
status: 'active' as AdminStatus

@ -26,8 +26,10 @@
<div class="filter-item">
<select v-model="searchFilters.areaId" @change="handleSearch">
<option value="">全部区域</option>
<option value="市区">市区</option>
<option value="校区">校区</option>
<option value="A">A区</option>
<option value="B">B区</option>
<option value="C">C区</option>
<option value="D">D区</option>
</select>
</div>

@ -165,8 +165,8 @@
<option value="">请选择维修人员</option>
<option
v-for="staff in filteredStaff"
:key="staff.id"
:value="staff.id"
:key="staff.repairmanId"
:value="staff.repairmanId"
>
{{ staff.repairmanName }} ({{ staff.phone }})
</option>
@ -221,12 +221,12 @@ interface TimeoutOrder {
}
//
interface MaintenanceStaff {
id: string
interface Repairman {
repairmanId: string
repairmanName: string
phone: string
areaId: string //
status: 'idle' | 'busy' | 'vacation' //
phone?: string
areaId: string
status?: 'idle' | 'busy' | 'vacation'
}
//
@ -251,7 +251,7 @@ const assignDialogVisible = ref(false)
const currentOrder = ref<TimeoutOrder | null>(null)
const selectedStaffId = ref('')
const assignRemark = ref('')
const allStaff = ref<MaintenanceStaff[]>([])
const allStaff = ref<Repairman[]>([])
// -
const loadTimeoutOrders = async () => {
@ -371,7 +371,7 @@ const loadMaintenanceStaff = async (areaId: string) => {
const response = await request<{
code: number
msg: string
data: MaintenanceStaff[]
data: Repairman[]
}>('/api/web/repairman/by-area/' + areaId, {
method: 'GET',
headers: {
@ -383,7 +383,13 @@ const loadMaintenanceStaff = async (areaId: string) => {
console.log('响应数据结构:', typeof response.data, response.data)
if (response.code === 200) {
allStaff.value = response.data || [] //
allStaff.value = (response.data || []).map(staff => ({
repairmanId: staff.repairmanId,
repairmanName: staff.repairmanName,
phone: staff.phone || '未知',
areaId: staff.areaId || '',
status: staff.status || 'idle'
}))
} else {
console.error('获取维修人员失败:', response.msg) //
alert('获取维修人员失败:' + response.msg)
@ -498,17 +504,28 @@ const handleStaffChange = () => {
}
//
// - JSON
const confirmAssign = async () => {
if (!currentOrder.value || !selectedStaffId.value) return
try {
console.log('🚀 发送派单请求使用JSON格式...')
// Token
const token = authStore.token
if (!token) {
router.push('/login')
alert('认证失败未找到用户Token请重新登录')
return
}
// API
// JSON
const requestData = {
orderId: currentOrder.value.id,
repairmanId: selectedStaffId.value
}
console.log('📤 JSON请求数据:', requestData)
const response = await request<{
code: number
msg: string
@ -516,29 +533,41 @@ const confirmAssign = async () => {
}>('/api/work-orders/assign', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/x-www-form-urlencoded'
'Content-Type': 'application/json', // JSON
'Authorization': `Bearer ${token}`
},
body: new URLSearchParams({
orderId: currentOrder.value.id,
repairmanId: selectedStaffId.value
})
body: JSON.stringify(requestData) // JSON
})
console.log('✅ 派单响应:', response)
if (response.code === 200 && response.data) {
alert('派单成功')
closeAssignDialog()
loadTimeoutOrders() //
loadTimeoutOrders()
} else {
const errorMsg = response.msg || '派单失败'
alert(errorMsg)
alert(`派单失败:${errorMsg} (错误码: ${response.code})`)
}
} catch (error: any) {
console.error('派单请求异常:', error)
alert('派单失败:' + (error.message || '网络错误'))
console.error('❌ 派单请求异常:', error)
//
if (error.message.includes('401') || error.message.includes('403')) {
alert('权限不足或认证失败:请重新登录')
authStore.logout()
} else if (error.message.includes('Unexpected token')) {
alert('派单失败后端期望JSON格式但可能接口配置不正确。请联系后端开发人员确认接口参数接收方式。')
} else {
alert('派单失败:' + (error.message || '未知错误'))
}
}
}
//
onMounted(() => {
console.log('Token:', authStore.token)

Loading…
Cancel
Save