diff --git a/.env.development b/.env.development index d8640f1..85a7063 100644 --- a/.env.development +++ b/.env.development @@ -11,4 +11,7 @@ VITE_BASE_URL=/ VITE_MOCK_API=true # 是否开启调试工具 -VITE_DEV_TOOLS=true \ No newline at end of file +VITE_DEV_TOOLS=true + +# 高德地图API密钥 +VITE_AMAP_KEY=your_amap_key_here \ No newline at end of file diff --git a/src/api/patrol/plan.js b/src/api/patrol/plan.js new file mode 100644 index 0000000..254f0cf --- /dev/null +++ b/src/api/patrol/plan.js @@ -0,0 +1,116 @@ +import request from '@/utils/request' + +/** + * 获取巡护计划列表 + * @param {Object} params - 查询参数 + * @param {number} [params.page=1] - 页码 + * @param {number} [params.pageSize=10] - 每页条数 + * @param {string} [params.keyword] - 搜索关键词 + * @param {string} [params.planType] - 计划类型:daily-日常巡护,special-专项巡护 + * @param {number} [params.status] - 状态:0-禁用,1-启用 + * @param {string} [params.startDate] - 开始日期 + * @param {string} [params.endDate] - 结束日期 + * @returns {Promise} 返回计划列表数据 + */ +export function getPlanList(params = {}) { + return request.get('/api/admin/patrol/plans', { + params: { + page: params.page || 1, + page_size: params.pageSize || 10, + keyword: params.keyword || undefined, + plan_type: params.planType || undefined, + status: params.status === undefined ? undefined : Number(params.status), + start_date: params.startDate || undefined, + end_date: params.endDate || undefined + } + }) +} + +/** + * 创建巡护计划 + * @param {Object} data - 计划数据 + * @param {string} data.plan_name - 计划名称,最大100字符 + * @param {string} data.plan_type - 计划类型:daily-日常巡护,special-专项巡护 + * @param {string} data.start_date - 开始日期,ISO日期格式 + * @param {string} data.end_date - 结束日期,必须大于start_date + * @param {Array} data.area_scope - 巡护区域范围点位列表,至少3个点位 + * @param {string} [data.task_frequency] - 任务频次:daily、weekly、monthly + * @param {string} [data.description] - 计划描述,最大1000字符 + * @param {number} [data.status] - 状态:0-禁用,1-启用,默认1 + * @returns {Promise} 返回创建结果 + */ +export function createPlan(data) { + return request.post('/api/admin/patrol/plans', data) +} + +/** + * 获取巡护计划详情 + * @param {string|number} id - 计划ID + * @returns {Promise} 返回计划详情数据 + */ +export function getPlanDetail(id) { + return request.get(`/api/admin/patrol/plans/${id}`) +} + +/** + * 更新巡护计划 + * @param {string|number} id - 计划ID + * @param {Object} data - 更新数据,参数同创建计划 + * @returns {Promise} 返回更新结果 + */ +export function updatePlan(id, data) { + return request.put(`/api/admin/patrol/plans/${id}`, data) +} + +/** + * 更新巡护计划状态 + * @param {string|number} id - 计划ID + * @param {number} status - 状态值:0-禁用,1-启用 + * @returns {Promise} 返回状态更新结果 + */ +export function updatePlanStatus(id, status) { + return request.put(`/api/admin/patrol/plans/${id}/status`, { + status: status === 1 ? 1 : 0 + }) +} + +/** + * 检查计划名称是否可用 + * @param {string} name - 计划名称 + * @param {string|number} [excludeId] - 排除的计划ID(用于编辑时检查) + * @returns {Promise} 返回检查结果 + */ +export function checkPlanName(name, excludeId) { + return request.get('/api/admin/patrol/plans/check-name', { + params: { + plan_name: name, + exclude_id: excludeId + } + }) +} + +/** + * 删除巡护计划 + * @param {string|number} id - 计划ID + * @returns {Promise} 返回删除结果 + */ +export function deletePlan(id) { + return request.delete(`/api/admin/patrol/plans/${id}`) +} + +/** + * 批量删除巡护计划 + * @param {Array} ids - 计划ID列表 + * @returns {Promise} 返回批量删除结果 + */ +export function batchDeletePlans(ids) { + return request.post('/api/admin/patrol/plans/batch/delete', { ids }) +} + +/** + * 获取巡护计划统计数据 + * @returns {Promise} 返回统计数据 + */ +export function getPlanStatistics() { + return request.get('/api/admin/patrol/plans/statistics') +} diff --git a/src/api/patrol/record.js b/src/api/patrol/record.js new file mode 100644 index 0000000..377a8ac --- /dev/null +++ b/src/api/patrol/record.js @@ -0,0 +1,163 @@ +import request from '@/utils/request' + +/** + * 创建巡护记录 + * @param {Object} data - 记录数据 + * @param {number} data.task_id - 关联的任务ID + * @param {string} data.record_time - 记录时间 + * @param {Array} data.patrol_points - 巡护点位列表 + * @param {string} data.description - 巡护描述 + * @param {Array} [data.images] - 图片列表 + * @param {Array} [data.files] - 附件列表 + * @param {Object} [data.weather] - 天气信息 + * @returns {Promise} 返回创建结果 + */ +export function createRecord(data) { + return request.post('/api/admin/patrol/records', data) +} + +/** + * 获取巡护记录列表 + * @param {Object} params - 查询参数 + * @param {number} [params.page=1] - 页码 + * @param {number} [params.pageSize=10] - 每页条数 + * @param {string} [params.keyword] - 搜索关键词 + * @param {string} [params.startDate] - 开始日期 + * @param {string} [params.endDate] - 结束日期 + * @param {number} [params.taskId] - 任务ID + * @param {number} [params.patrolerId] - 巡护员ID + * @returns {Promise} 返回记录列表数据 + */ +export function getRecordList(params = {}) { + return request.get('/api/admin/patrol/records', { + params: { + page: params.page || 1, + page_size: params.pageSize || 10, + keyword: params.keyword || undefined, + start_date: params.startDate || undefined, + end_date: params.endDate || undefined, + task_id: params.taskId || undefined, + patroler_id: params.patrolerId || undefined + } + }) +} + +/** + * 获取巡护记录详情 + * @param {string|number} id - 记录ID + * @returns {Promise} 返回记录详情数据 + */ +export function getRecordDetail(id) { + return request.get(`/api/admin/patrol/records/${id}`) +} + +/** + * 导出巡护记录 + * @param {Object} params - 导出参数 + * @param {string} [params.startDate] - 开始日期 + * @param {string} [params.endDate] - 结束日期 + * @param {number} [params.taskId] - 任务ID + * @param {number} [params.patrolerId] - 巡护员ID + * @param {string} [params.format='excel'] - 导出格式 + * @returns {Promise} 返回二进制文件流 + */ +export function exportRecords(params = {}) { + return request.get('/api/admin/patrol/records/export/list', { + params: { + start_date: params.startDate || undefined, + end_date: params.endDate || undefined, + task_id: params.taskId || undefined, + patroler_id: params.patrolerId || undefined, + format: params.format || 'excel' + }, + responseType: 'blob', + headers: { + 'Accept': 'application/vnd.ms-excel' + } + }) +} + +/** + * 获取巡护统计信息 + * @returns {Promise} 返回统计数据 + */ +export function getRecordStatistics() { + return request.get('/api/admin/patrol/records/statistics/overview') +} + +/** + * 获取任务相关的巡护记录 + * @param {string|number} taskId - 任务ID + * @param {Object} params - 查询参数 + * @param {number} [params.page=1] - 页码 + * @param {number} [params.pageSize=10] - 每页条数 + * @returns {Promise} 返回任务相关的记录列表 + */ +export function getTaskRecords(taskId, params = {}) { + return request.get(`/api/admin/patrol/records/task/${taskId}`, { + params: { + page: params.page || 1, + page_size: params.pageSize || 10 + } + }) +} + +/** + * 更新巡护记录 + * @param {string|number} id - 记录ID + * @param {Object} data - 更新数据 + * @returns {Promise} 返回更新结果 + */ +export function updateRecord(id, data) { + return request.put(`/api/admin/patrol/records/${id}`, data) +} + +/** + * 删除巡护记录 + * @param {string|number} id - 记录ID + * @returns {Promise} 返回删除结果 + */ +export function deleteRecord(id) { + return request.delete(`/api/admin/patrol/records/${id}`) +} + +/** + * 批量删除巡护记录 + * @param {Array} ids - 记录ID列表 + * @returns {Promise} 返回批量删除结果 + */ +export function batchDeleteRecords(ids) { + return request.post('/api/admin/patrol/records/batch/delete', { ids }) +} + +/** + * 上传巡护记录图片 + * @param {File} file - 图片文件 + * @returns {Promise} 返回上传结果 + */ +export function uploadRecordImage(file) { + const formData = new FormData() + formData.append('image', file) + + return request.post('/api/admin/patrol/records/upload/image', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} + +/** + * 上传巡护记录附件 + * @param {File} file - 附件文件 + * @returns {Promise} 返回上传结果 + */ +export function uploadRecordFile(file) { + const formData = new FormData() + formData.append('file', file) + + return request.post('/api/admin/patrol/records/upload/file', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} \ No newline at end of file diff --git a/src/api/patrol/task.js b/src/api/patrol/task.js new file mode 100644 index 0000000..2d2e4af --- /dev/null +++ b/src/api/patrol/task.js @@ -0,0 +1,93 @@ +import request from '@/utils/request' + +/** + * 创建巡护任务 + * @param {Object} data - 任务数据 + * @param {string} data.title - 任务标题 + * @param {string} data.description - 任务描述 + * @param {string} data.start_time - 开始时间 + * @param {string} data.end_time - 结束时间 + * @param {number} data.executor_id - 执行人ID + * @param {Array} data.patrol_points - 巡护点位列表 + * @param {string} data.patrol_area - 巡护区域 + * @param {number} data.priority - 优先级:0-普通,1-重要,2-紧急 + * @returns {Promise} 返回创建结果 + */ +export function createTask(data) { + return request.post('/api/admin/patrol/tasks', data) +} + +/** + * 获取巡护任务列表 + * @param {Object} params - 查询参数 + * @param {number} [params.page=1] - 页码 + * @param {number} [params.pageSize=10] - 每页条数 + * @param {string} [params.keyword] - 搜索关键词 + * @param {string} [params.status] - 任务状态 + * @param {string} [params.startDate] - 开始日期 + * @param {string} [params.endDate] - 结束日期 + * @param {number} [params.executorId] - 执行人ID + * @returns {Promise} 返回任务列表数据 + */ +export function getTaskList(params = {}) { + return request.get('/api/admin/patrol/tasks', { + params: { + page: params.page || 1, + pageSize: params.pageSize || 10, + keyword: params.keyword || undefined, + status: params.status || undefined, + start_date: params.startDate || undefined, + end_date: params.endDate || undefined, + executor_id: params.executorId || undefined + } + }) +} + +/** + * 获取巡护任务详情 + * @param {string|number} id - 任务ID + * @returns {Promise} 返回任务详情数据 + */ +export function getTaskDetail(id) { + return request.get(`/api/admin/patrol/tasks/${id}`) +} + +/** + * 更新巡护任务 + * @param {string|number} id - 任务ID + * @param {Object} data - 更新数据 + * @returns {Promise} 返回更新结果 + */ +export function updateTask(id, data) { + return request.put(`/api/admin/patrol/plans/${id}`, data) +} + +/** + * 获取执行人的任务列表 + * @param {string|number} executorId - 执行人ID + * @param {Object} params - 查询参数 + * @param {number} [params.page=1] - 页码 + * @param {number} [params.pageSize=10] - 每页条数 + * @param {string} [params.status] - 任务状态 + * @returns {Promise} 返回执行人的任务列表 + */ +export function getExecutorTasks(executorId, params = {}) { + return request.get(`/api/admin/patrol/tasks/executor/${executorId}`, { + params: { + page: params.page || 1, + pageSize: params.pageSize || 10, + status: params.status || undefined + } + }) +} + +/** + * 取消巡护任务 + * @param {string|number} id - 任务ID + * @param {Object} data - 取消原因等数据 + * @param {string} data.reason - 取消原因 + * @returns {Promise} 返回取消结果 + */ +export function cancelTask(id, data = {}) { + return request.post(`/api/admin/patrol/tasks/${id}/cancel`, data) +} \ No newline at end of file diff --git a/src/layout/AdminLayout.vue b/src/layout/AdminLayout.vue index 4534706..5b8b3a9 100644 --- a/src/layout/AdminLayout.vue +++ b/src/layout/AdminLayout.vue @@ -91,6 +91,7 @@ const handleLogout = () => { :default-active="activeMenu" class="el-menu-vertical" @select="handleSelect" + unique-opened > @@ -130,6 +131,7 @@ const handleLogout = () => { 巡护管理 + 巡护计划 巡护任务 巡护记录 安防事件 diff --git a/src/router/index.js b/src/router/index.js index d508240..ca9f0fe 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -69,6 +69,11 @@ const router = createRouter({ name: 'PatrolEvents', component: () => import('../views/patrol/events/index.vue') }, + { + path: 'patrol/plans', + name: 'PatrolPlans', + component: () => import('../views/patrol/plans/index.vue') + }, { path: 'patrol/records', name: 'PatrolRecords', @@ -112,7 +117,8 @@ const router = createRouter({ { path: 'system/data', name: 'DataManagement', - component: () => import('../views/system/data/index.vue') + component: () => import('@/views/system/data/index.vue'), + meta: { title: '数据管理', icon: 'data' } }, { path: 'system/carousel', diff --git a/src/utils/map.js b/src/utils/map.js new file mode 100644 index 0000000..277dc0c --- /dev/null +++ b/src/utils/map.js @@ -0,0 +1,54 @@ +/** + * 将经纬度转换为地址 + * @param {number} longitude - 经度 + * @param {number} latitude - 纬度 + * @returns {Promise} 返回地址字符串 + */ +export async function getAddressFromLocation(longitude, latitude) { + try { + // 使用高德地图API进行地理编码 + const response = await fetch( + `https://restapi.amap.com/v3/geocode/regeo?key=${import.meta.env.VITE_AMAP_KEY}&location=${longitude},${latitude}` + ); + const data = await response.json(); + + if (data.status === '1' && data.regeocode) { + return data.regeocode.formatted_address; + } + return null; + } catch (error) { + console.error('地理编码错误:', error); + return null; + } +} + +/** + * 处理巡护路线点位信息,添加地址信息 + * @param {Array} points - 路线点位数组 + * @returns {Promise} 返回带有地址信息的点位数组 + */ +export async function getRoutePointsWithAddress(points) { + if (!points || !Array.isArray(points)) return []; + + try { + const pointsWithAddress = await Promise.all( + points.map(async (point, index) => { + const address = await getAddressFromLocation(point.longitude, point.latitude); + return { + ...point, + index: index + 1, + address: address || '未能获取地址' + }; + }) + ); + + return pointsWithAddress; + } catch (error) { + console.error('处理路线点位错误:', error); + return points.map((point, index) => ({ + ...point, + index: index + 1, + address: '未能获取地址' + })); + } +} \ No newline at end of file diff --git a/src/views/monitor/environment/index.vue b/src/views/monitor/environment/index.vue index d2ffeb6..4eb4fe1 100644 --- a/src/views/monitor/environment/index.vue +++ b/src/views/monitor/environment/index.vue @@ -468,7 +468,7 @@ onMounted(() => { \ No newline at end of file diff --git a/src/views/patrol/records/index.vue b/src/views/patrol/records/index.vue index 8dceebc..497a567 100644 --- a/src/views/patrol/records/index.vue +++ b/src/views/patrol/records/index.vue @@ -1,229 +1,1128 @@ diff --git a/src/views/patrol/tasks/index.vue b/src/views/patrol/tasks/index.vue index 0fd6e10..0d2eeec 100644 --- a/src/views/patrol/tasks/index.vue +++ b/src/views/patrol/tasks/index.vue @@ -1,38 +1,241 @@ @@ -283,7 +471,7 @@ const resetFilters = () => {
- {{ statistics.statusCount.进行中 }} + {{ statistics.statusCount.执行中 }} 个任务
@@ -296,7 +484,7 @@ const resetFilters = () => {
- {{ statistics.statusCount.待开始 }} + {{ statistics.statusCount.待执行 }} 个任务
@@ -338,10 +526,10 @@ const resetFilters = () => { style="width: 200px; margin-left: 16px" clearable > - - - - + + + + { style="width: 200px; margin-left: 16px" clearable > - - - + + + { - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - + +
+
巡护路线
+
+
+
点位{{ point.index }}:
+
经度{{ point.longitude }}, 纬度{{ point.latitude }}
+
+ {{ point.address }} +
+
+
+ 暂无路线信息 +
+
@@ -477,42 +770,84 @@ const resetFilters = () => { @close="handleDialogClose" > - - + + - - + + - - - - - - - - + + + 常规 + 紧急 - + - - + + + + + + + + - + + + + + + + + + + + + + + + + + + + + @@ -523,6 +858,19 @@ const resetFilters = () => { + + +
+ +
@@ -723,4 +1071,140 @@ const resetFilters = () => { .mt-20 { margin-top: 20px; } + +.pagination-container { + margin-top: 20px; + display: flex; + justify-content: flex-end; +} + +.task-detail { + .detail-section { + margin-bottom: 24px; + padding: 16px; + background-color: #f8f9fa; + border-radius: 8px; + + &:last-child { + margin-bottom: 0; + } + + .section-title { + font-size: 16px; + font-weight: 500; + color: #303133; + margin-bottom: 16px; + padding-left: 8px; + border-left: 4px solid var(--el-color-primary); + } + + .plan-info { + width: 100%; + :deep(.el-descriptions) { + padding: 8px; + .el-descriptions__label { + width: 120px; + justify-content: flex-end; + color: #606266; + } + .el-descriptions__content { + color: #303133; + } + } + } + } + + .detail-row { + display: flex; + margin-bottom: 16px; + + &:last-child { + margin-bottom: 0; + } + + .detail-item { + flex: 1; + display: flex; + align-items: flex-start; + + &.full-width { + flex: 0 0 100%; + } + + strong { + width: 100px; + margin-right: 8px; + color: #606266; + text-align: right; + } + + span { + flex: 1; + line-height: 1.5; + } + } + } +} + +.plan-info { + padding: 8px 0; + + .plan-item { + display: flex; + align-items: center; + margin-bottom: 8px; + + &:last-child { + margin-bottom: 0; + } + + &.plan-name { + .value { + flex: 1; + word-break: break-all; + } + } + + .label { + min-width: 80px; + color: #606266; + } + + .value { + color: #303133; + font-weight: 500; + } + } +} + +.route-points { + .route-point { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; + padding: 8px; + background-color: #f8f9fa; + border-radius: 4px; + + &:last-child { + margin-bottom: 0; + } + + .point-title { + font-weight: 500; + min-width: 60px; + color: #606266; + } + + .point-coords { + color: #606266; + margin-right: auto; + } + + .point-address { + flex-shrink: 0; + } + } +}