Add delete functionality for patrol events, tasks, and plans
- Implement single and batch delete methods for events, tasks, and plans - Add delete API endpoints in respective modules - Update Vue components with delete buttons and confirmation dialogs - Implement validation to prevent deleting tasks/plans with active status or related records - Enhance user experience with clear error messages and selective deletion
This commit is contained in:
parent
1972b43dec
commit
90107b33d3
40
src/api/monitor/observation.js
Normal file
40
src/api/monitor/observation.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询观察记录列表
|
||||||
|
export function getObservationList(query) {
|
||||||
|
return request.get('/api/admin/observations', { params: query })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取观察记录详细信息
|
||||||
|
export function getObservation(id) {
|
||||||
|
return request.get(`/api/admin/observations/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建观察记录
|
||||||
|
export function createObservation(data) {
|
||||||
|
return request.post('/api/admin/observations', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新观察记录
|
||||||
|
export function updateObservation(id, data) {
|
||||||
|
return request.put(`/api/admin/observations/${id}`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新观察记录状态(审核)
|
||||||
|
export function reviewObservation(id, data) {
|
||||||
|
return request.put(`/api/admin/observations/${id}/status`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除观察记录
|
||||||
|
export function deleteObservation(id) {
|
||||||
|
return request.delete(`/api/admin/observations/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出观察记录
|
||||||
|
export function exportObservation(query) {
|
||||||
|
return request({
|
||||||
|
url: '/monitor/observation/export',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -114,4 +114,22 @@ export function batchExportEvents(ids) {
|
|||||||
*/
|
*/
|
||||||
export function getEventStatistics() {
|
export function getEventStatistics() {
|
||||||
return request.get('/api/admin/patrol/events/statistics/overview')
|
return request.get('/api/admin/patrol/events/statistics/overview')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除单个事件
|
||||||
|
* @param {string|number} id - 事件ID
|
||||||
|
* @returns {Promise} 返回删除结果
|
||||||
|
*/
|
||||||
|
export function deleteEvent(id) {
|
||||||
|
return request.delete(`/api/admin/patrol/events/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除事件
|
||||||
|
* @param {Array<string|number>} ids - 事件ID列表
|
||||||
|
* @returns {Promise} 返回批量删除结果
|
||||||
|
*/
|
||||||
|
export function batchDeleteEvents(ids) {
|
||||||
|
return request.post('/api/admin/patrol/events/batch/delete', { ids })
|
||||||
}
|
}
|
||||||
@ -91,6 +91,13 @@ export function checkPlanName(name, excludeId) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除巡护计划
|
* 删除巡护计划
|
||||||
|
* 只能删除以下状态的计划:
|
||||||
|
* - 未开始的计划
|
||||||
|
* - 已完成的计划
|
||||||
|
* - 已取消的计划
|
||||||
|
* 不能删除:
|
||||||
|
* - 进行中的计划
|
||||||
|
* - 已有关联巡护任务的计划
|
||||||
* @param {string|number} id - 计划ID
|
* @param {string|number} id - 计划ID
|
||||||
* @returns {Promise} 返回删除结果
|
* @returns {Promise} 返回删除结果
|
||||||
*/
|
*/
|
||||||
@ -100,6 +107,13 @@ export function deletePlan(id) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除巡护计划
|
* 批量删除巡护计划
|
||||||
|
* 只能删除以下状态的计划:
|
||||||
|
* - 未开始的计划
|
||||||
|
* - 已完成的计划
|
||||||
|
* - 已取消的计划
|
||||||
|
* 不能删除:
|
||||||
|
* - 进行中的计划
|
||||||
|
* - 已有关联巡护任务的计划
|
||||||
* @param {Array<string|number>} ids - 计划ID列表
|
* @param {Array<string|number>} ids - 计划ID列表
|
||||||
* @returns {Promise} 返回批量删除结果
|
* @returns {Promise} 返回批量删除结果
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -90,4 +90,36 @@ export function getExecutorTasks(executorId, params = {}) {
|
|||||||
*/
|
*/
|
||||||
export function cancelTask(id, data = {}) {
|
export function cancelTask(id, data = {}) {
|
||||||
return request.post(`/api/admin/patrol/tasks/${id}/cancel`, data)
|
return request.post(`/api/admin/patrol/tasks/${id}/cancel`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除巡护任务
|
||||||
|
* 只能删除以下状态的任务:
|
||||||
|
* - 未开始的任务
|
||||||
|
* - 已完成的任务
|
||||||
|
* - 已取消的任务
|
||||||
|
* 不能删除:
|
||||||
|
* - 进行中的任务
|
||||||
|
* - 已有巡护记录的任务
|
||||||
|
* @param {string|number} id - 任务ID
|
||||||
|
* @returns {Promise} 返回删除结果
|
||||||
|
*/
|
||||||
|
export function deleteTask(id) {
|
||||||
|
return request.delete(`/api/admin/patrol/tasks/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除巡护任务
|
||||||
|
* 只能删除以下状态的任务:
|
||||||
|
* - 未开始的任务
|
||||||
|
* - 已完成的任务
|
||||||
|
* - 已取消的任务
|
||||||
|
* 不能删除:
|
||||||
|
* - 进行中的任务
|
||||||
|
* - 已有巡护记录的任务
|
||||||
|
* @param {Array<string|number>} ids - 任务ID列表
|
||||||
|
* @returns {Promise} 返回批量删除结果
|
||||||
|
*/
|
||||||
|
export function batchDeleteTasks(ids) {
|
||||||
|
return request.post('/api/admin/patrol/tasks/batch/delete', { ids })
|
||||||
}
|
}
|
||||||
@ -124,6 +124,7 @@ const handleLogout = () => {
|
|||||||
</template>
|
</template>
|
||||||
<el-menu-item index="/monitor/species">物种监测</el-menu-item>
|
<el-menu-item index="/monitor/species">物种监测</el-menu-item>
|
||||||
<el-menu-item index="/monitor/environment">环境监测</el-menu-item>
|
<el-menu-item index="/monitor/environment">环境监测</el-menu-item>
|
||||||
|
<el-menu-item index="/monitor/observations">观测管理</el-menu-item>
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
|
|
||||||
<el-sub-menu index="patrol">
|
<el-sub-menu index="patrol">
|
||||||
|
|||||||
@ -59,6 +59,11 @@ const router = createRouter({
|
|||||||
name: 'EnvironmentMonitor',
|
name: 'EnvironmentMonitor',
|
||||||
component: () => import('../views/monitor/environment/index.vue')
|
component: () => import('../views/monitor/environment/index.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'monitor/observations',
|
||||||
|
name: 'ObservationsMonitor',
|
||||||
|
component: () => import('../views/monitor/observations/index.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'patrol/tasks',
|
path: 'patrol/tasks',
|
||||||
name: 'PatrolTasks',
|
name: 'PatrolTasks',
|
||||||
|
|||||||
@ -452,8 +452,8 @@ onMounted(() => {
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="导出格式">
|
<el-form-item label="导出格式">
|
||||||
<el-radio-group v-model="exportForm.format">
|
<el-radio-group v-model="exportForm.format">
|
||||||
<el-radio label="excel">Excel</el-radio>
|
<el-radio value="excel">Excel</el-radio>
|
||||||
<el-radio label="csv">CSV</el-radio>
|
<el-radio value="csv">CSV</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|||||||
485
src/views/monitor/observations/index.vue
Normal file
485
src/views/monitor/observations/index.vue
Normal file
@ -0,0 +1,485 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 搜索条件 -->
|
||||||
|
<el-card class="filter-container">
|
||||||
|
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||||
|
<el-form-item label="记录标题">
|
||||||
|
<el-input v-model="queryParams.title" placeholder="请输入记录标题" clearable style="width: 240px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 240px">
|
||||||
|
<el-option label="待审核" :value="0" />
|
||||||
|
<el-option label="已通过" :value="1" />
|
||||||
|
<el-option label="已拒绝" :value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 数据列表 -->
|
||||||
|
<el-card class="list-container">
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="observationList"
|
||||||
|
style="width: 100%"
|
||||||
|
border
|
||||||
|
>
|
||||||
|
<el-table-column type="index" label="序号" width="80" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ (queryParams.page - 1) * queryParams.page_size + scope.$index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="title" label="记录标题" min-width="150" show-overflow-tooltip align="center"/>
|
||||||
|
<el-table-column label="记录人" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.user_real_name }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="物种" width="120" align="center" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tooltip :content="row.species_latin_name" placement="top">
|
||||||
|
<span>{{ row.species_name }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="图片" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-image
|
||||||
|
v-if="row.image_urls && row.image_urls.length > 0"
|
||||||
|
:src="row.image_urls[0]"
|
||||||
|
:preview-src-list="row.image_urls"
|
||||||
|
fit="cover"
|
||||||
|
style="width: 50px; height: 50px"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="位置" width="150" show-overflow-tooltip align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tooltip :content="'经度:' + row.longitude + ', 纬度:' + row.latitude" placement="top">
|
||||||
|
<span>{{ row.location_description }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="weather" label="天气" width="100" align="center" />
|
||||||
|
<el-table-column prop="temperature" label="温度" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.temperature }}°C
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getStatusType(row.status)">
|
||||||
|
{{ getStatusText(row.status) }}
|
||||||
|
</el-tag>
|
||||||
|
<br v-if="row.reviewer_real_name" />
|
||||||
|
<small v-if="row.reviewer_real_name">审核人: {{ row.reviewer_real_name }}</small>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="created_at" label="创建时间" width="180" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDateTime(row.created_at) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button
|
||||||
|
type="primary" link
|
||||||
|
@click="handleView(row)"
|
||||||
|
>查看</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="row.status === 0"
|
||||||
|
type="warning" link
|
||||||
|
@click="handleReview(row)"
|
||||||
|
>审核</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger" link
|
||||||
|
@click="handleDelete(row)"
|
||||||
|
>删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="fixed-pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-show="total > queryParams.page_size"
|
||||||
|
v-model:current-page="queryParams.page"
|
||||||
|
v-model:page-size="queryParams.page_size"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
background
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查看对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="viewDialogVisible"
|
||||||
|
title="查看观察记录"
|
||||||
|
width="800px"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<el-descriptions :column="2" border>
|
||||||
|
<el-descriptions-item label="记录标题">{{ viewForm.title }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="物种">{{ getSpeciesName(viewForm.species_id) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="位置描述">{{ viewForm.location_description }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="经纬度">{{ viewForm.longitude }}, {{ viewForm.latitude }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="天气">{{ viewForm.weather }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="温度">{{ viewForm.temperature }}°C</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="记录内容" :span="2">{{ viewForm.content }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="状态">
|
||||||
|
<el-tag :type="getStatusType(viewForm.status)">{{ getStatusText(viewForm.status) }}</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="审核信息" v-if="viewForm.status !== 0">
|
||||||
|
<div>审核人: {{ viewForm.reviewer_real_name }}</div>
|
||||||
|
<div>审核意见: {{ viewForm.review_comment }}</div>
|
||||||
|
<div>审核时间: {{ formatDateTime(viewForm.review_time) }}</div>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="图片" :span="2" v-if="viewForm.image_urls && viewForm.image_urls.length > 0">
|
||||||
|
<el-image
|
||||||
|
v-for="(url, index) in viewForm.image_urls"
|
||||||
|
:key="index"
|
||||||
|
:src="url"
|
||||||
|
:preview-src-list="viewForm.image_urls"
|
||||||
|
fit="cover"
|
||||||
|
style="width: 100px; height: 100px; margin-right: 10px"
|
||||||
|
/>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="视频" :span="2" v-if="viewForm.video_urls && viewForm.video_urls.length > 0">
|
||||||
|
<video
|
||||||
|
v-for="(url, index) in viewForm.video_urls"
|
||||||
|
:key="index"
|
||||||
|
:src="url"
|
||||||
|
controls
|
||||||
|
style="width: 200px; margin-right: 10px"
|
||||||
|
/>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="viewDialogVisible = false">关 闭</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 审核对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="reviewDialogVisible"
|
||||||
|
title="审核观察记录"
|
||||||
|
width="800px"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<el-descriptions :column="2" border>
|
||||||
|
<el-descriptions-item label="记录标题">{{ reviewForm.title }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="物种">{{ getSpeciesName(reviewForm.species_id) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="位置描述">{{ reviewForm.location_description }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="经纬度">{{ reviewForm.longitude }}, {{ reviewForm.latitude }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="天气">{{ reviewForm.weather }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="温度">{{ reviewForm.temperature }}°C</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="记录内容" :span="2">{{ reviewForm.content }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="图片" :span="2" v-if="reviewForm.image_urls && reviewForm.image_urls.length > 0">
|
||||||
|
<el-image
|
||||||
|
v-for="(url, index) in reviewForm.image_urls"
|
||||||
|
:key="index"
|
||||||
|
:src="url"
|
||||||
|
:preview-src-list="reviewForm.image_urls"
|
||||||
|
fit="cover"
|
||||||
|
style="width: 100px; height: 100px; margin-right: 10px"
|
||||||
|
/>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="视频" :span="2" v-if="reviewForm.video_urls && reviewForm.video_urls.length > 0">
|
||||||
|
<video
|
||||||
|
v-for="(url, index) in reviewForm.video_urls"
|
||||||
|
:key="index"
|
||||||
|
:src="url"
|
||||||
|
controls
|
||||||
|
style="width: 200px; margin-right: 10px"
|
||||||
|
/>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<el-divider>审核信息</el-divider>
|
||||||
|
<el-form ref="reviewFormRef" :model="reviewForm" :rules="reviewRules" label-width="100px">
|
||||||
|
<el-form-item label="审核结果" prop="review_status">
|
||||||
|
<el-radio-group v-model="reviewForm.review_status">
|
||||||
|
<el-radio :value="1">通过</el-radio>
|
||||||
|
<el-radio :value="2">拒绝</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="审核意见" prop="review_comment">
|
||||||
|
<el-input
|
||||||
|
v-model="reviewForm.review_comment"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="请输入审核意见"
|
||||||
|
:rows="3"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="reviewDialogVisible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="submitReview">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 图片预览 -->
|
||||||
|
<el-dialog v-model="picturePreviewVisible" append-to-body>
|
||||||
|
<img w-full :src="picturePreviewUrl" alt="Preview Image" />
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { getObservationList, reviewObservation, deleteObservation } from '@/api/monitor/observation'
|
||||||
|
import { formatDateTime } from '@/utils/format'
|
||||||
|
import { sortArrayByField } from '@/utils/sort'
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive({
|
||||||
|
page: 1,
|
||||||
|
page_size: 10,
|
||||||
|
title: '',
|
||||||
|
status: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// 数据列表
|
||||||
|
const loading = ref(false)
|
||||||
|
const observationList = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
// 物种列表
|
||||||
|
const speciesList = ref([
|
||||||
|
{ id: 1, name: '东方白鹳' },
|
||||||
|
// 这里需要从后端获取物种列表
|
||||||
|
])
|
||||||
|
|
||||||
|
// 图片预览
|
||||||
|
const picturePreviewVisible = ref(false)
|
||||||
|
const picturePreviewUrl = ref('')
|
||||||
|
|
||||||
|
// 查看表单
|
||||||
|
const viewDialogVisible = ref(false)
|
||||||
|
const viewForm = ref({})
|
||||||
|
|
||||||
|
// 审核表单
|
||||||
|
const reviewDialogVisible = ref(false)
|
||||||
|
const reviewFormRef = ref()
|
||||||
|
const reviewForm = ref({})
|
||||||
|
|
||||||
|
// 审核表单校验规则
|
||||||
|
const reviewRules = {
|
||||||
|
review_status: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
|
||||||
|
review_comment: [{ required: true, message: '请输入审核意见', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取列表数据
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// 过滤掉空值参数
|
||||||
|
const params = {
|
||||||
|
page: queryParams.page,
|
||||||
|
page_size: queryParams.page_size
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryParams.title) {
|
||||||
|
params.title = queryParams.title
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryParams.status !== undefined) {
|
||||||
|
params.status = queryParams.status
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await getObservationList(params)
|
||||||
|
if (res.success) {
|
||||||
|
// 对数据进行正序排列
|
||||||
|
observationList.value = sortArrayByField(res.data.list, 'created_at', 'asc')
|
||||||
|
total.value = res.data.pagination.total
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '获取数据失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取观察记录列表失败:', error)
|
||||||
|
ElMessage.error('获取数据失败')
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询操作
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.page = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置操作
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.title = ''
|
||||||
|
queryParams.status = undefined
|
||||||
|
queryParams.page = 1
|
||||||
|
queryParams.page_size = 10
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除操作
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`确认要删除观察记录"${row.title}"吗?`,
|
||||||
|
'警告',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
).then(async () => {
|
||||||
|
try {
|
||||||
|
await deleteObservation(row.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
// 如果当前页只有一条数据,且不是第一页,则跳转到上一页
|
||||||
|
if (observationList.value.length === 1 && queryParams.page > 1) {
|
||||||
|
queryParams.page--
|
||||||
|
}
|
||||||
|
getList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除失败:', error)
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// 取消删除操作
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态显示
|
||||||
|
const getStatusType = (status) => {
|
||||||
|
const statusMap = {
|
||||||
|
0: 'warning',
|
||||||
|
1: 'success',
|
||||||
|
2: 'danger'
|
||||||
|
}
|
||||||
|
return statusMap[status]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusText = (status) => {
|
||||||
|
const statusMap = {
|
||||||
|
0: '待审核',
|
||||||
|
1: '已通过',
|
||||||
|
2: '已拒绝'
|
||||||
|
}
|
||||||
|
return statusMap[status]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理分页大小变化
|
||||||
|
const handleSizeChange = (val) => {
|
||||||
|
queryParams.page_size = val
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理页码变化
|
||||||
|
const handleCurrentChange = (val) => {
|
||||||
|
queryParams.page = val
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片预览
|
||||||
|
const handlePictureCardPreview = (file) => {
|
||||||
|
picturePreviewUrl.value = file.url
|
||||||
|
picturePreviewVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取物种名称
|
||||||
|
const getSpeciesName = (id) => {
|
||||||
|
const species = speciesList.value.find(item => item.id === id)
|
||||||
|
return species ? species.name : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看操作
|
||||||
|
const handleView = (row) => {
|
||||||
|
viewForm.value = { ...row }
|
||||||
|
viewDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审核操作
|
||||||
|
const handleReview = (row) => {
|
||||||
|
reviewForm.value = {
|
||||||
|
id: row.id,
|
||||||
|
title: row.title,
|
||||||
|
species_id: row.species_id,
|
||||||
|
content: row.content,
|
||||||
|
location_description: row.location_description,
|
||||||
|
longitude: Number(row.longitude),
|
||||||
|
latitude: Number(row.latitude),
|
||||||
|
weather: row.weather,
|
||||||
|
temperature: Number(row.temperature),
|
||||||
|
image_urls: [...(row.image_urls || [])],
|
||||||
|
video_urls: [...(row.video_urls || [])],
|
||||||
|
status: row.status,
|
||||||
|
review_status: 1,
|
||||||
|
review_comment: ''
|
||||||
|
}
|
||||||
|
reviewDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交审核
|
||||||
|
const submitReview = async () => {
|
||||||
|
await reviewFormRef.value.validate()
|
||||||
|
try {
|
||||||
|
const formData = {
|
||||||
|
status: reviewForm.value.review_status,
|
||||||
|
review_comment: reviewForm.value.review_comment
|
||||||
|
}
|
||||||
|
await reviewObservation(reviewForm.value.id, formData)
|
||||||
|
ElMessage.success('审核成功')
|
||||||
|
reviewDialogVisible.value = false
|
||||||
|
getList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('审核失败:', error)
|
||||||
|
ElMessage.error('审核失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.filter-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-pagination {
|
||||||
|
position: fixed;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gray-500 {
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,8 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Search, Refresh, Plus, Download } from '@element-plus/icons-vue'
|
import { Search, Refresh, Plus, Download, Delete } from '@element-plus/icons-vue'
|
||||||
import { getEventList, updateEventStatus, createEvent, getEventDetail, getEventStatistics, exportEvents, batchExportEvents } from '@/api/patrol/event'
|
import { getEventList, updateEventStatus, createEvent, getEventDetail, getEventStatistics, exportEvents, batchExportEvents, deleteEvent, batchDeleteEvents } from '@/api/patrol/event'
|
||||||
import { formatDateTime } from '@/utils/format'
|
import { formatDateTime } from '@/utils/format'
|
||||||
|
|
||||||
// 表格数据
|
// 表格数据
|
||||||
@ -15,7 +15,8 @@ const statistics = ref({
|
|||||||
pending: 0,
|
pending: 0,
|
||||||
processing: 0,
|
processing: 0,
|
||||||
processed: 0,
|
processed: 0,
|
||||||
closed: 0
|
closed: 0,
|
||||||
|
avgHandlingTime: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// 分页配置
|
// 分页配置
|
||||||
@ -102,15 +103,15 @@ const getList = async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
tableData.value = res.data.list
|
// 将数据数组反转为正序
|
||||||
|
tableData.value = [...res.data.list].reverse()
|
||||||
total.value = Number(res.data.pagination.total)
|
total.value = Number(res.data.pagination.total)
|
||||||
currentPage.value = Number(res.data.pagination.page || 1)
|
currentPage.value = Number(res.data.pagination.page || 1)
|
||||||
pageSize.value = Number(res.data.pagination.page_size || 10)
|
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '获取列表失败')
|
ElMessage.error(res.message || '获取列表失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取安防事件列表错误:', error)
|
console.error('获取事件列表错误:', error)
|
||||||
ElMessage.error('获取列表失败')
|
ElMessage.error('获取列表失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@ -122,10 +123,23 @@ const getStatistics = async () => {
|
|||||||
try {
|
try {
|
||||||
const res = await getEventStatistics()
|
const res = await getEventStatistics()
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
statistics.value = res.data
|
// 从状态分布中获取各状态的数量
|
||||||
|
const statusData = res.data.status_distribution
|
||||||
|
statistics.value = {
|
||||||
|
total: res.data.total_events || 0,
|
||||||
|
pending: statusData.find(item => item.status === 0)?.count || 0,
|
||||||
|
processing: statusData.find(item => item.status === 1)?.count || 0,
|
||||||
|
processed: statusData.find(item => item.status === 2)?.count || 0,
|
||||||
|
closed: statusData.find(item => item.status === 3)?.count || 0,
|
||||||
|
avgHandlingTime: res.data.avg_handling_time?.hours || 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('获取统计数据失败:', res.message)
|
||||||
|
ElMessage.error('获取统计数据失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取统计数据错误:', error)
|
console.error('获取统计数据错误:', error)
|
||||||
|
ElMessage.error('获取统计数据失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,6 +381,81 @@ const handleSelectionChange = (selection) => {
|
|||||||
selectedRows.value = selection
|
selectedRows.value = selection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加删除相关的计算属性和方法
|
||||||
|
const hasSelectedDeletableRows = computed(() => {
|
||||||
|
return selectedRows.value.some(row => row.handling_status === 2 || row.handling_status === 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理单个删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
if (row.handling_status === 0 || row.handling_status === 1) {
|
||||||
|
ElMessage.warning('待处理或处理中的事件不能删除')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
'确认删除该事件吗?此操作不可恢复',
|
||||||
|
'警告',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
).then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await deleteEvent(row.id)
|
||||||
|
if (res.success) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
getStatistics()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除事件错误:', error)
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
const deletableRows = selectedRows.value.filter(
|
||||||
|
row => row.handling_status === 2 || row.handling_status === 3
|
||||||
|
)
|
||||||
|
|
||||||
|
if (deletableRows.length === 0) {
|
||||||
|
ElMessage.warning('没有可删除的事件(只能删除已处理或已关闭的事件)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`确认删除选中的 ${deletableRows.length} 个事件吗?此操作不可恢复`,
|
||||||
|
'警告',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
).then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await batchDeleteEvents(deletableRows.map(row => row.id))
|
||||||
|
if (res.success) {
|
||||||
|
ElMessage.success('批量删除成功')
|
||||||
|
getList()
|
||||||
|
getStatistics()
|
||||||
|
// 清空选择
|
||||||
|
selectedRows.value = []
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '批量删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量删除事件错误:', error)
|
||||||
|
ElMessage.error('批量删除失败')
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
getStatistics()
|
getStatistics()
|
||||||
@ -437,6 +526,7 @@ onMounted(() => {
|
|||||||
<el-button type="primary" :icon="Plus" @click="handleCreate">新增事件</el-button>
|
<el-button type="primary" :icon="Plus" @click="handleCreate">新增事件</el-button>
|
||||||
<el-button type="success" :icon="Download" @click="handleExport">全部导出</el-button>
|
<el-button type="success" :icon="Download" @click="handleExport">全部导出</el-button>
|
||||||
<el-button type="success" :icon="Download" @click="handleBatchExport" :disabled="selectedRows.length === 0">批量导出</el-button>
|
<el-button type="success" :icon="Download" @click="handleBatchExport" :disabled="selectedRows.length === 0">批量导出</el-button>
|
||||||
|
<el-button type="danger" :icon="Delete" @click="handleBatchDelete" :disabled="!hasSelectedDeletableRows">批量删除</el-button>
|
||||||
<el-button :icon="Refresh" @click="handleRefresh">刷新</el-button>
|
<el-button :icon="Refresh" @click="handleRefresh">刷新</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -568,6 +658,12 @@ onMounted(() => {
|
|||||||
link
|
link
|
||||||
@click="handleClose(row)"
|
@click="handleClose(row)"
|
||||||
>关闭</el-button>
|
>关闭</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
link
|
||||||
|
@click="handleDelete(row)"
|
||||||
|
v-if="row.handling_status === 2 || row.handling_status === 3"
|
||||||
|
>删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="header-title">巡护计划管理</span>
|
<span class="header-title">巡护计划管理</span>
|
||||||
<div class="header-btns">
|
<div class="header-btns">
|
||||||
<el-button type="primary" :icon="Plus" @click="newPlanDialogVisible = true">新建计划</el-button>
|
<el-button type="primary" :icon="Plus" @click="handleCreate">新建计划</el-button>
|
||||||
|
<el-button :icon="Delete" @click="handleBatchDelete" :disabled="!hasSelectedDeletableRows">批量删除</el-button>
|
||||||
<el-button :icon="Refresh" @click="handleRefresh">刷新</el-button>
|
<el-button :icon="Refresh" @click="handleRefresh">刷新</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -126,7 +127,7 @@
|
|||||||
|
|
||||||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button link type="primary" @click="handleView(row)">查看</el-button>
|
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -134,7 +135,14 @@
|
|||||||
>
|
>
|
||||||
{{ row.status === 1 ? '禁用' : '启用' }}
|
{{ row.status === 1 ? '禁用' : '启用' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(row)"
|
||||||
|
v-if="canDeletePlan(row)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -155,25 +163,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 新建计划弹窗 -->
|
<!-- 巡护计划表单弹窗 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="newPlanDialogVisible"
|
v-model="planDialogVisible"
|
||||||
title="新建巡护计划"
|
:title="isEdit ? '编辑巡护计划' : '新建巡护计划'"
|
||||||
width="600px"
|
width="600px"
|
||||||
@close="handleDialogClose"
|
@close="handleDialogClose"
|
||||||
>
|
>
|
||||||
<el-form
|
<el-form
|
||||||
ref="newPlanFormRef"
|
ref="planFormRef"
|
||||||
:model="newPlanForm"
|
:model="planForm"
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
label-width="100px"
|
label-width="100px"
|
||||||
|
class="plan-form"
|
||||||
>
|
>
|
||||||
<el-form-item label="计划名称" prop="plan_name">
|
<el-form-item label="计划名称" prop="plan_name">
|
||||||
<el-input v-model="newPlanForm.plan_name" placeholder="请输入计划名称" />
|
<el-input v-model="planForm.plan_name" placeholder="请输入计划名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="计划类型" prop="plan_type">
|
<el-form-item label="计划类型" prop="plan_type">
|
||||||
<el-radio-group v-model="newPlanForm.plan_type">
|
<el-radio-group v-model="planForm.plan_type">
|
||||||
<el-radio value="daily">日常巡护</el-radio>
|
<el-radio value="daily">日常巡护</el-radio>
|
||||||
<el-radio value="special">专项巡护</el-radio>
|
<el-radio value="special">专项巡护</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
@ -183,7 +192,7 @@
|
|||||||
<el-col :span="11">
|
<el-col :span="11">
|
||||||
<el-form-item prop="start_date">
|
<el-form-item prop="start_date">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="newPlanForm.start_date"
|
v-model="planForm.start_date"
|
||||||
type="date"
|
type="date"
|
||||||
placeholder="开始日期"
|
placeholder="开始日期"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@ -197,7 +206,7 @@
|
|||||||
<el-col :span="11">
|
<el-col :span="11">
|
||||||
<el-form-item prop="end_date">
|
<el-form-item prop="end_date">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="newPlanForm.end_date"
|
v-model="planForm.end_date"
|
||||||
type="date"
|
type="date"
|
||||||
placeholder="结束日期"
|
placeholder="结束日期"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@ -208,7 +217,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="任务频次" prop="task_frequency">
|
<el-form-item label="任务频次" prop="task_frequency">
|
||||||
<el-radio-group v-model="newPlanForm.task_frequency">
|
<el-radio-group v-model="planForm.task_frequency">
|
||||||
<el-radio value="daily">每日</el-radio>
|
<el-radio value="daily">每日</el-radio>
|
||||||
<el-radio value="weekly">每周</el-radio>
|
<el-radio value="weekly">每周</el-radio>
|
||||||
<el-radio value="monthly">每月</el-radio>
|
<el-radio value="monthly">每月</el-radio>
|
||||||
@ -259,7 +268,7 @@
|
|||||||
|
|
||||||
<el-form-item label="计划描述" prop="description">
|
<el-form-item label="计划描述" prop="description">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="newPlanForm.description"
|
v-model="planForm.description"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="4"
|
:rows="4"
|
||||||
placeholder="请输入计划描述"
|
placeholder="请输入计划描述"
|
||||||
@ -267,7 +276,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="计划状态" prop="status">
|
<el-form-item label="计划状态" prop="status">
|
||||||
<el-radio-group v-model="newPlanForm.status">
|
<el-radio-group v-model="planForm.status">
|
||||||
<el-radio :value="1">启用</el-radio>
|
<el-radio :value="1">启用</el-radio>
|
||||||
<el-radio :value="0">禁用</el-radio>
|
<el-radio :value="0">禁用</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
@ -275,74 +284,15 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="newPlanDialogVisible = false">取消</el-button>
|
<el-button @click="planDialogVisible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="handleNewPlanSubmit">确定</el-button>
|
<el-button type="primary" @click="handlePlanSubmit">确定</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 计划详情弹窗 -->
|
|
||||||
<el-dialog
|
|
||||||
v-model="detailDialogVisible"
|
|
||||||
title="巡护计划详情"
|
|
||||||
width="600px"
|
|
||||||
>
|
|
||||||
<el-descriptions v-if="currentPlan" :column="1" border>
|
|
||||||
<el-descriptions-item label="计划名称">
|
|
||||||
{{ currentPlan.plan_name }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="计划类型">
|
|
||||||
<el-tag :type="currentPlan.plan_type === 'daily' ? 'info' : 'warning'">
|
|
||||||
{{ currentPlan.plan_type === 'daily' ? '日常巡护' : '专项巡护' }}
|
|
||||||
</el-tag>
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="执行时间">
|
|
||||||
{{ formatDateTime(currentPlan.start_date, 'YYYY-MM-DD') }} 至
|
|
||||||
{{ formatDateTime(currentPlan.end_date, 'YYYY-MM-DD') }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="任务频次">
|
|
||||||
{{ {
|
|
||||||
'daily': '每日',
|
|
||||||
'weekly': '每周',
|
|
||||||
'monthly': '每月'
|
|
||||||
}[currentPlan.task_frequency] || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="巡护区域">
|
|
||||||
<div v-if="currentPlan.area_scope?.length">
|
|
||||||
<div v-for="(point, index) in currentPlan.area_scope" :key="index">
|
|
||||||
经度: {{ point.longitude }}, 纬度: {{ point.latitude }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="计划状态">
|
|
||||||
<el-tag :type="currentPlan.status === 1 ? 'success' : 'info'">
|
|
||||||
{{ currentPlan.status === 1 ? '启用' : '禁用' }}
|
|
||||||
</el-tag>
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="计划描述">
|
|
||||||
{{ currentPlan.description || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="创建时间">
|
|
||||||
{{ formatDateTime(currentPlan.created_at) }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="更新时间">
|
|
||||||
{{ formatDateTime(currentPlan.updated_at) }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Search, Plus, Delete, Refresh } from '@element-plus/icons-vue'
|
import { Search, Plus, Delete, Refresh } from '@element-plus/icons-vue'
|
||||||
import { formatDateTime } from '@/utils/format'
|
import { formatDateTime } from '@/utils/format'
|
||||||
@ -375,7 +325,10 @@ const dateRange = ref(['', ''])
|
|||||||
|
|
||||||
// 新建计划相关
|
// 新建计划相关
|
||||||
const newPlanDialogVisible = ref(false)
|
const newPlanDialogVisible = ref(false)
|
||||||
const newPlanForm = reactive({
|
const planDialogVisible = ref(false)
|
||||||
|
const isEdit = ref(false)
|
||||||
|
const planForm = reactive({
|
||||||
|
id: '',
|
||||||
plan_name: '',
|
plan_name: '',
|
||||||
plan_type: 'daily',
|
plan_type: 'daily',
|
||||||
description: '',
|
description: '',
|
||||||
@ -386,7 +339,7 @@ const newPlanForm = reactive({
|
|||||||
status: 1
|
status: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
const newPlanFormRef = ref()
|
const planFormRef = ref()
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const rules = {
|
const rules = {
|
||||||
@ -400,7 +353,7 @@ const rules = {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await checkPlanName(value, newPlanForm.id)
|
const res = await checkPlanName(value, planForm.id)
|
||||||
if (res.success && !res.data.available) {
|
if (res.success && !res.data.available) {
|
||||||
callback(new Error('计划名称已存在'))
|
callback(new Error('计划名称已存在'))
|
||||||
} else {
|
} else {
|
||||||
@ -420,7 +373,7 @@ const rules = {
|
|||||||
{ required: true, message: '请选择结束日期', trigger: 'change' },
|
{ required: true, message: '请选择结束日期', trigger: 'change' },
|
||||||
{
|
{
|
||||||
validator: (rule, value, callback) => {
|
validator: (rule, value, callback) => {
|
||||||
if (value && newPlanForm.start_date && value <= newPlanForm.start_date) {
|
if (value && planForm.start_date && value <= planForm.start_date) {
|
||||||
callback(new Error('结束日期必须大于开始日期'))
|
callback(new Error('结束日期必须大于开始日期'))
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
@ -473,7 +426,7 @@ const getList = async () => {
|
|||||||
|
|
||||||
// 重置表单
|
// 重置表单
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
Object.assign(newPlanForm, {
|
Object.assign(planForm, {
|
||||||
plan_name: '',
|
plan_name: '',
|
||||||
plan_type: 'daily',
|
plan_type: 'daily',
|
||||||
description: '',
|
description: '',
|
||||||
@ -484,45 +437,71 @@ const resetForm = () => {
|
|||||||
status: 1
|
status: 1
|
||||||
})
|
})
|
||||||
addressList.value = [] // 清空地址列表
|
addressList.value = [] // 清空地址列表
|
||||||
if (newPlanFormRef.value) {
|
if (planFormRef.value) {
|
||||||
newPlanFormRef.value.resetFields()
|
planFormRef.value.resetFields()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理新建计划提交
|
// 处理新建计划提交
|
||||||
const handleNewPlanSubmit = async () => {
|
const handlePlanSubmit = async () => {
|
||||||
if (!newPlanFormRef.value) return
|
if (!planFormRef.value) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await newPlanFormRef.value.validate()
|
await planFormRef.value.validate()
|
||||||
|
|
||||||
|
// 确保area_scope数据格式正确
|
||||||
|
const formattedAreaScope = addressList.value.map(point => ({
|
||||||
|
longitude: point.longitude,
|
||||||
|
latitude: point.latitude,
|
||||||
|
address: point.address
|
||||||
|
}))
|
||||||
|
|
||||||
const submitData = {
|
const submitData = {
|
||||||
plan_name: newPlanForm.plan_name.trim(),
|
plan_name: planForm.plan_name.trim(),
|
||||||
plan_type: newPlanForm.plan_type,
|
plan_type: planForm.plan_type,
|
||||||
description: newPlanForm.description?.trim(),
|
description: planForm.description?.trim(),
|
||||||
start_date: newPlanForm.start_date,
|
start_date: planForm.start_date,
|
||||||
end_date: newPlanForm.end_date,
|
end_date: planForm.end_date,
|
||||||
area_scope: newPlanForm.area_scope,
|
area_scope: formattedAreaScope,
|
||||||
task_frequency: newPlanForm.task_frequency,
|
task_frequency: planForm.task_frequency,
|
||||||
status: newPlanForm.status
|
status: planForm.status
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await createPlan(submitData)
|
if (isEdit.value) {
|
||||||
if (res.success) {
|
const res = await updatePlan(planForm.id, submitData)
|
||||||
ElMessage.success('创建成功')
|
if (res.success) {
|
||||||
newPlanDialogVisible.value = false
|
ElMessage.success('更新成功')
|
||||||
getList()
|
planDialogVisible.value = false
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '更新失败')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '创建失败')
|
const res = await createPlan(submitData)
|
||||||
|
if (res.success) {
|
||||||
|
ElMessage.success('创建成功')
|
||||||
|
planDialogVisible.value = false
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '创建失败')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建计划错误:', error)
|
console.error('提交计划错误:', error)
|
||||||
ElMessage.error('创建失败,请检查输入是否正确')
|
ElMessage.error('提交失败,请检查输入是否正确')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理关闭弹窗
|
// 处理新建按钮点击
|
||||||
|
const handleCreate = () => {
|
||||||
|
isEdit.value = false
|
||||||
|
resetForm()
|
||||||
|
planDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理对话框关闭
|
||||||
const handleDialogClose = () => {
|
const handleDialogClose = () => {
|
||||||
|
isEdit.value = false
|
||||||
resetForm()
|
resetForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -554,26 +533,6 @@ const handleSizeChange = (size) => {
|
|||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计划详情相关
|
|
||||||
const detailDialogVisible = ref(false)
|
|
||||||
const currentPlan = ref(null)
|
|
||||||
|
|
||||||
// 查看计划详情
|
|
||||||
const handleView = async (row) => {
|
|
||||||
try {
|
|
||||||
const res = await getPlanDetail(row.id)
|
|
||||||
if (res.success) {
|
|
||||||
currentPlan.value = res.data
|
|
||||||
detailDialogVisible.value = true
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.message || '获取计划详情失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取计划详情错误:', error)
|
|
||||||
ElMessage.error('获取计划详情失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新计划状态
|
// 更新计划状态
|
||||||
const handleStatusChange = async (row) => {
|
const handleStatusChange = async (row) => {
|
||||||
try {
|
try {
|
||||||
@ -591,9 +550,33 @@ const handleStatusChange = async (row) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除计划
|
// 选中的行
|
||||||
|
const selectedRows = ref([])
|
||||||
|
|
||||||
|
// 判断计划是否可删除
|
||||||
|
const canDeletePlan = (plan) => {
|
||||||
|
// 不能删除进行中的计划
|
||||||
|
if (plan.status === 1) return false
|
||||||
|
// 不能删除有关联任务的计划
|
||||||
|
if (plan.has_related_tasks) return false
|
||||||
|
// 可以删除未开始、已完成、已取消的计划
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算是否有可删除的选中行
|
||||||
|
const hasSelectedDeletableRows = computed(() => {
|
||||||
|
return selectedRows.value.some(row => canDeletePlan(row))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理单个删除
|
||||||
const handleDelete = (row) => {
|
const handleDelete = (row) => {
|
||||||
ElMessageBox.confirm('确定要删除该计划吗?', '提示', {
|
// 先检查计划是否可删除
|
||||||
|
if (!canDeletePlan(row)) {
|
||||||
|
ElMessage.warning('该巡护计划已有关联的巡护任务或正在进行中,不能删除')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm('确认删除该巡护计划吗?此操作不可恢复', '警告', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
@ -607,41 +590,62 @@ const handleDelete = (row) => {
|
|||||||
ElMessage.error(res.message || '删除失败')
|
ElMessage.error(res.message || '删除失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除计划错误:', error)
|
console.error('删除巡护计划错误:', error)
|
||||||
ElMessage.error('删除失败')
|
// 根据错误响应给出具体提示
|
||||||
|
if (error.response?.status === 400) {
|
||||||
|
ElMessage.warning('该巡护计划已有关联的巡护任务或正在进行中,不能删除')
|
||||||
|
} else {
|
||||||
|
ElMessage.error('删除失败,请稍后重试')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量删除计划
|
// 处理批量删除
|
||||||
const handleBatchDelete = () => {
|
const handleBatchDelete = () => {
|
||||||
if (selectedPlans.value.length === 0) {
|
// 过滤出可删除的计划
|
||||||
ElMessage.warning('请选择要删除的计划')
|
const deletableRows = selectedRows.value.filter(row => canDeletePlan(row))
|
||||||
|
|
||||||
|
if (deletableRows.length === 0) {
|
||||||
|
ElMessage.warning('选中的计划中没有可删除的计划(不能删除进行中或已有关联任务的计划)')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ElMessageBox.confirm(`确定要删除选中的 ${selectedPlans.value.length} 个计划吗?`, '提示', {
|
ElMessageBox.confirm(
|
||||||
confirmButtonText: '确定',
|
`确认删除选中的 ${deletableRows.length} 个计划吗?此操作不可恢复`,
|
||||||
cancelButtonText: '取消',
|
'警告',
|
||||||
type: 'warning'
|
{
|
||||||
}).then(async () => {
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
).then(async () => {
|
||||||
try {
|
try {
|
||||||
const ids = selectedPlans.value.map(plan => plan.id)
|
const res = await batchDeletePlans(deletableRows.map(row => row.id))
|
||||||
const res = await batchDeletePlans(ids)
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
ElMessage.success('批量删除成功')
|
ElMessage.success('批量删除成功')
|
||||||
selectedPlans.value = []
|
|
||||||
getList()
|
getList()
|
||||||
|
// 清空选择
|
||||||
|
selectedRows.value = []
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '批量删除失败')
|
ElMessage.error(res.message || '批量删除失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('批量删除计划错误:', error)
|
console.error('批量删除巡护计划错误:', error)
|
||||||
ElMessage.error('批量删除失败')
|
if (error.response?.status === 400) {
|
||||||
|
ElMessage.warning('部分计划已有关联的巡护任务或正在进行中,不能删除')
|
||||||
|
} else {
|
||||||
|
ElMessage.error('批量删除失败,请稍后重试')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理选择变化
|
||||||
|
const handleSelectionChange = (selection) => {
|
||||||
|
selectedRows.value = selection
|
||||||
|
}
|
||||||
|
|
||||||
// 添加刷新方法
|
// 添加刷新方法
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
getList()
|
getList()
|
||||||
@ -685,7 +689,7 @@ const handleAddAddress = async () => {
|
|||||||
const result = await convertAddressToCoords(addressInput.value.trim())
|
const result = await convertAddressToCoords(addressInput.value.trim())
|
||||||
if (result) {
|
if (result) {
|
||||||
addressList.value.push(result)
|
addressList.value.push(result)
|
||||||
newPlanForm.area_scope = addressList.value.map(item => ({
|
planForm.area_scope = addressList.value.map(item => ({
|
||||||
longitude: item.longitude,
|
longitude: item.longitude,
|
||||||
latitude: item.latitude
|
latitude: item.latitude
|
||||||
}))
|
}))
|
||||||
@ -696,18 +700,60 @@ const handleAddAddress = async () => {
|
|||||||
// 删除地址
|
// 删除地址
|
||||||
const handleRemoveAddress = (index) => {
|
const handleRemoveAddress = (index) => {
|
||||||
addressList.value.splice(index, 1)
|
addressList.value.splice(index, 1)
|
||||||
newPlanForm.area_scope = addressList.value.map(item => ({
|
planForm.area_scope = addressList.value.map(item => ({
|
||||||
longitude: item.longitude,
|
longitude: item.longitude,
|
||||||
latitude: item.latitude
|
latitude: item.latitude
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 编辑计划相关
|
||||||
|
const handleEdit = async (row) => {
|
||||||
|
try {
|
||||||
|
const res = await getPlanDetail(row.id)
|
||||||
|
if (res.success) {
|
||||||
|
// 填充表单数据
|
||||||
|
Object.assign(planForm, {
|
||||||
|
id: row.id,
|
||||||
|
plan_name: res.data.plan_name,
|
||||||
|
plan_type: res.data.plan_type,
|
||||||
|
description: res.data.description || '',
|
||||||
|
start_date: res.data.start_date,
|
||||||
|
end_date: res.data.end_date,
|
||||||
|
task_frequency: res.data.task_frequency || 'daily',
|
||||||
|
status: res.data.status,
|
||||||
|
area_scope: [] // 初始化area_scope为空数组
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新地址列表和area_scope,确保数据格式一致
|
||||||
|
const formattedAreaScope = (res.data.area_scope || []).map(point => ({
|
||||||
|
address: point.address || '未知地址',
|
||||||
|
longitude: point.longitude,
|
||||||
|
latitude: point.latitude
|
||||||
|
}))
|
||||||
|
|
||||||
|
addressList.value = formattedAreaScope
|
||||||
|
planForm.area_scope = formattedAreaScope
|
||||||
|
|
||||||
|
isEdit.value = true
|
||||||
|
planDialogVisible.value = true
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '获取计划详情失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取计划详情错误:', error)
|
||||||
|
ElMessage.error('获取计划详情失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@use "../../../styles/variables.scss" as *;
|
||||||
|
@use "sass:color";
|
||||||
|
|
||||||
.patrol-plans {
|
.patrol-plans {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background-color: #f0f2f5;
|
background-color: #f0f2f5;
|
||||||
@ -901,21 +947,283 @@ onMounted(() => {
|
|||||||
|
|
||||||
.address-input-container {
|
.address-input-container {
|
||||||
.address-input {
|
.address-input {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
.address-input-field {
|
.address-input-field {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-input-group__append {
|
||||||
|
background-color: $primary-color;
|
||||||
|
border-color: $primary-color;
|
||||||
|
color: white;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: color.adjust($primary-color, $lightness: 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.address-list {
|
.address-list {
|
||||||
margin-bottom: 12px;
|
margin: 16px 0;
|
||||||
max-height: 240px;
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 200px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
|
||||||
|
.el-table__inner-wrapper {
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
color: $text-primary;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 8px 0;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 8px 0;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table__body-wrapper {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.address-tip {
|
.address-tip {
|
||||||
margin-top: 8px;
|
margin-top: 12px;
|
||||||
|
|
||||||
|
:deep(.el-alert) {
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.el-alert__title {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-form {
|
||||||
|
.el-form-item {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: $text-primary;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper),
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #c0c4cc;
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-focus {
|
||||||
|
border-color: $primary-color;
|
||||||
|
box-shadow: 0 0 0 1px $primary-color inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
padding: 12px 16px;
|
||||||
|
min-height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-select) {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-input__wrapper {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-date-editor) {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-input__wrapper {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-radio-group) {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
.el-radio {
|
||||||
|
margin-right: 0;
|
||||||
|
|
||||||
|
.el-radio__label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: $text-regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-radio__input.is-checked + .el-radio__label {
|
||||||
|
color: $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-input-container {
|
||||||
|
.address-input {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.address-input-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input-group__append {
|
||||||
|
background-color: $primary-color;
|
||||||
|
border-color: $primary-color;
|
||||||
|
color: white;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: color.adjust($primary-color, $lightness: 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-list {
|
||||||
|
margin: 16px 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
|
||||||
|
.el-table__inner-wrapper {
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
color: $text-primary;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 8px 0;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 8px 0;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table__body-wrapper {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-tip {
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
:deep(.el-alert) {
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.el-alert__title {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog) {
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.el-dialog__header {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
background-color: #fafafa;
|
||||||
|
|
||||||
|
.el-dialog__title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__headerbtn {
|
||||||
|
top: 20px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__body {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__footer {
|
||||||
|
padding: 16px 24px;
|
||||||
|
margin: 0;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
background-color: #fafafa;
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
min-width: 100px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-button--primary {
|
||||||
|
background: linear-gradient(45deg, $primary-color, color.adjust($primary-color, $lightness: 10%));
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted } from "vue";
|
import { ref, reactive, computed, onMounted } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { Search, Plus, InfoFilled } from '@element-plus/icons-vue';
|
import { Search, Plus, InfoFilled, Delete, Refresh } from '@element-plus/icons-vue';
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
import { getRoutePointsWithAddress } from '@/utils/map';
|
import { getRoutePointsWithAddress } from '@/utils/map';
|
||||||
import {
|
import {
|
||||||
@ -9,9 +9,12 @@ import {
|
|||||||
createTask,
|
createTask,
|
||||||
getTaskDetail,
|
getTaskDetail,
|
||||||
updateTask,
|
updateTask,
|
||||||
cancelTask
|
cancelTask,
|
||||||
|
deleteTask,
|
||||||
|
batchDeleteTasks
|
||||||
} from '@/api/patrol/task';
|
} from '@/api/patrol/task';
|
||||||
import { getUserList } from '@/api/user';
|
import { getUserList } from '@/api/user';
|
||||||
|
import { getPlanList, getPlanDetail } from '@/api/patrol/plan';
|
||||||
|
|
||||||
// 表格数据
|
// 表格数据
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
@ -20,6 +23,12 @@ const loading = ref(false);
|
|||||||
// 用户列表数据
|
// 用户列表数据
|
||||||
const userList = ref([]);
|
const userList = ref([]);
|
||||||
|
|
||||||
|
// 选中的行
|
||||||
|
const selectedRows = ref([]);
|
||||||
|
|
||||||
|
// 计划列表数据
|
||||||
|
const planList = ref([]);
|
||||||
|
|
||||||
// 获取任务列表
|
// 获取任务列表
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@ -69,6 +78,22 @@ const getUserData = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取计划列表
|
||||||
|
const getPlanData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getPlanList({ status: 1 }); // 只获取启用状态的计划
|
||||||
|
if (res.success && Array.isArray(res.data.list)) {
|
||||||
|
planList.value = res.data.list;
|
||||||
|
} else {
|
||||||
|
console.error('获取计划列表数据格式错误:', res);
|
||||||
|
planList.value = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取计划列表错误:', error);
|
||||||
|
planList.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 优先级转换
|
// 优先级转换
|
||||||
const getPriorityText = (priority) => {
|
const getPriorityText = (priority) => {
|
||||||
const map = {
|
const map = {
|
||||||
@ -117,7 +142,23 @@ const handleNewTaskSubmit = async () => {
|
|||||||
try {
|
try {
|
||||||
await newTaskFormRef.value.validate();
|
await newTaskFormRef.value.validate();
|
||||||
|
|
||||||
// 构造提交数据
|
// 先获取计划详情,检查巡护日期是否在计划时间范围内
|
||||||
|
const planRes = await getPlanDetail(newTaskForm.plan_id);
|
||||||
|
if (!planRes.success) {
|
||||||
|
ElMessage.error('获取计划详情失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const planStartDate = new Date(planRes.data.start_date);
|
||||||
|
const planEndDate = new Date(planRes.data.end_date);
|
||||||
|
const patrolDate = new Date(newTaskForm.patrol_date);
|
||||||
|
|
||||||
|
if (patrolDate < planStartDate || patrolDate > planEndDate) {
|
||||||
|
ElMessage.error('巡护日期必须在计划时间范围内');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造提交数据,严格按照API要求的格式
|
||||||
const submitData = {
|
const submitData = {
|
||||||
plan_id: Number(newTaskForm.plan_id),
|
plan_id: Number(newTaskForm.plan_id),
|
||||||
task_name: newTaskForm.task_name.trim(),
|
task_name: newTaskForm.task_name.trim(),
|
||||||
@ -125,11 +166,11 @@ const handleNewTaskSubmit = async () => {
|
|||||||
patrol_date: newTaskForm.patrol_date,
|
patrol_date: newTaskForm.patrol_date,
|
||||||
start_time: newTaskForm.start_time,
|
start_time: newTaskForm.start_time,
|
||||||
end_time: newTaskForm.end_time,
|
end_time: newTaskForm.end_time,
|
||||||
route_points: newTaskForm.route_points,
|
route_points: newTaskForm.route_points || [],
|
||||||
executor_ids: newTaskForm.executor_ids,
|
executor_ids: newTaskForm.executor_ids,
|
||||||
description: newTaskForm.description?.trim(),
|
description: newTaskForm.description?.trim() || '',
|
||||||
priority: newTaskForm.priority,
|
priority: Number(newTaskForm.priority),
|
||||||
status: newTaskForm.status
|
status: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await createTask(submitData);
|
const res = await createTask(submitData);
|
||||||
@ -225,6 +266,7 @@ const resetFilters = () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getUserData(); // 获取用户列表
|
getUserData(); // 获取用户列表
|
||||||
|
getPlanData(); // 获取计划列表
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -444,6 +486,233 @@ const getUserNames = (ids) => {
|
|||||||
return `${ids.length}人`;
|
return `${ids.length}人`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 判断任务是否可删除
|
||||||
|
const canDeleteTask = (task) => {
|
||||||
|
// 不能删除进行中的任务
|
||||||
|
if (task.status === 1) return false
|
||||||
|
// 不能删除已有巡护记录的任务
|
||||||
|
if (task.has_patrol_records) return false
|
||||||
|
// 可以删除未开始、已完成、已取消的任务
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算是否有可删除的选中行
|
||||||
|
const hasSelectedDeletableRows = computed(() => {
|
||||||
|
return selectedRows.value.some(row => canDeleteTask(row))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理单个删除
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
// 先检查任务是否可删除
|
||||||
|
if (!canDeleteTask(row)) {
|
||||||
|
ElMessage.warning('该巡护任务正在进行中或已有巡护记录,不能删除')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm('确认删除该巡护任务吗?此操作不可恢复', '警告', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await deleteTask(row.id)
|
||||||
|
if (res.success) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除巡护任务错误:', error)
|
||||||
|
// 根据错误响应给出具体提示
|
||||||
|
if (error.response?.status === 400) {
|
||||||
|
ElMessage.warning('该巡护任务正在进行中或已有巡护记录,不能删除')
|
||||||
|
} else {
|
||||||
|
ElMessage.error('删除失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
// 过滤出可删除的任务
|
||||||
|
const deletableRows = selectedRows.value.filter(row => canDeleteTask(row))
|
||||||
|
|
||||||
|
if (deletableRows.length === 0) {
|
||||||
|
ElMessage.warning('选中的任务中没有可删除的任务(不能删除进行中或已有巡护记录的任务)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`确认删除选中的 ${deletableRows.length} 个任务吗?此操作不可恢复`,
|
||||||
|
'警告',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
).then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await batchDeleteTasks(deletableRows.map(row => row.id))
|
||||||
|
if (res.success) {
|
||||||
|
ElMessage.success('批量删除成功')
|
||||||
|
getList()
|
||||||
|
// 清空选择
|
||||||
|
selectedRows.value = []
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '批量删除失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量删除巡护任务错误:', error)
|
||||||
|
if (error.response?.status === 400) {
|
||||||
|
ElMessage.warning('部分任务正在进行中或已有巡护记录,不能删除')
|
||||||
|
} else {
|
||||||
|
ElMessage.error('批量删除失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理选择变化
|
||||||
|
const handleSelectionChange = (selection) => {
|
||||||
|
selectedRows.value = selection
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单对话框
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('新增任务')
|
||||||
|
const formMode = ref('create')
|
||||||
|
const form = ref({
|
||||||
|
task_name: '',
|
||||||
|
description: '',
|
||||||
|
patrol_date: '',
|
||||||
|
start_time: '',
|
||||||
|
end_time: '',
|
||||||
|
executor_ids: [],
|
||||||
|
route_points: [],
|
||||||
|
task_type: 'regular',
|
||||||
|
priority: 2,
|
||||||
|
plan_id: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单规则
|
||||||
|
const formRules = {
|
||||||
|
task_name: [
|
||||||
|
{ required: true, message: '请输入任务名称', trigger: 'blur' },
|
||||||
|
{ min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
description: [
|
||||||
|
{ required: true, message: '请输入任务描述', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
patrol_date: [
|
||||||
|
{ required: true, message: '请选择巡护日期', trigger: 'change' }
|
||||||
|
],
|
||||||
|
start_time: [
|
||||||
|
{ required: true, message: '请选择开始时间', trigger: 'change' }
|
||||||
|
],
|
||||||
|
end_time: [
|
||||||
|
{ required: true, message: '请选择结束时间', trigger: 'change' }
|
||||||
|
],
|
||||||
|
executor_ids: [
|
||||||
|
{ required: true, message: '请选择执行人', trigger: 'change' }
|
||||||
|
],
|
||||||
|
plan_id: [
|
||||||
|
{ required: true, message: '请输入计划ID', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const formRef = ref(null)
|
||||||
|
|
||||||
|
// 编辑任务
|
||||||
|
const handleEdit = async (row) => {
|
||||||
|
try {
|
||||||
|
const res = await getTaskDetail(row.id)
|
||||||
|
if (res.success) {
|
||||||
|
formMode.value = 'edit'
|
||||||
|
dialogTitle.value = '编辑任务'
|
||||||
|
form.value = {
|
||||||
|
id: row.id,
|
||||||
|
task_name: res.data.task_name,
|
||||||
|
description: res.data.description,
|
||||||
|
patrol_date: res.data.patrol_date,
|
||||||
|
start_time: res.data.start_time,
|
||||||
|
end_time: res.data.end_time,
|
||||||
|
executor_ids: res.data.executor_ids || [],
|
||||||
|
route_points: res.data.route_points || [],
|
||||||
|
task_type: res.data.task_type || 'regular',
|
||||||
|
priority: res.data.priority || 2,
|
||||||
|
plan_id: res.data.plan_id
|
||||||
|
}
|
||||||
|
dialogVisible.value = true
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '获取任务详情失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取任务详情错误:', error)
|
||||||
|
ElMessage.error('获取任务详情失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
await formRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
try {
|
||||||
|
const submitData = {
|
||||||
|
task_name: form.value.task_name.trim(),
|
||||||
|
description: form.value.description.trim(),
|
||||||
|
patrol_date: form.value.patrol_date,
|
||||||
|
start_time: form.value.start_time,
|
||||||
|
end_time: form.value.end_time,
|
||||||
|
executor_ids: form.value.executor_ids,
|
||||||
|
route_points: form.value.route_points,
|
||||||
|
task_type: form.value.task_type,
|
||||||
|
priority: Number(form.value.priority),
|
||||||
|
plan_id: Number(form.value.plan_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
let res
|
||||||
|
if (formMode.value === 'create') {
|
||||||
|
res = await createTask(submitData)
|
||||||
|
} else {
|
||||||
|
res = await updateTask(form.value.id, submitData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.success) {
|
||||||
|
ElMessage.success(`${formMode.value === 'create' ? '新增' : '编辑'}成功`)
|
||||||
|
dialogVisible.value = false
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || `${formMode.value === 'create' ? '新增' : '编辑'}失败`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${formMode.value === 'create' ? '新增' : '编辑'}任务错误:`, error)
|
||||||
|
ElMessage.error('操作失败,请检查输入是否正确')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理计划选择变更
|
||||||
|
const handlePlanChange = async (planId) => {
|
||||||
|
if (!planId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getPlanDetail(planId);
|
||||||
|
if (res.success) {
|
||||||
|
// 重置巡护日期,确保在计划时间范围内选择
|
||||||
|
newTaskForm.patrol_date = '';
|
||||||
|
// 可以在这里添加其他相关字段的处理
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取计划详情错误:', error);
|
||||||
|
ElMessage.error('获取计划详情失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -554,16 +823,23 @@ const getUserNames = (ids) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<el-button @click="resetFilters">重置</el-button>
|
<el-button @click="resetFilters">重置</el-button>
|
||||||
<el-button type="primary" @click="handleNewTask">新建任务</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮区域 -->
|
||||||
|
<div class="operation-btns">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="handleNewTask">新建任务</el-button>
|
||||||
|
<el-button :icon="Delete" @click="handleBatchDelete" :disabled="!hasSelectedDeletableRows">批量删除</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="handleSearch">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 巡护任务列表 -->
|
<!-- 巡护任务列表 -->
|
||||||
<el-table
|
<el-table
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
style="width: 100%"
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||||
<el-table-column prop="plan_id" label="计划ID" width="90" align="center">
|
<el-table-column prop="plan_id" label="计划ID" width="90" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@ -620,21 +896,19 @@ const getUserNames = (ids) => {
|
|||||||
{{ formatDateTime(row.created_at) }}
|
{{ formatDateTime(row.created_at) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" link @click="handleView(row)">查看</el-button>
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.status === 0"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
@click="handleEdit(row)"
|
@click="handleEdit(row)"
|
||||||
>编辑</el-button>
|
>编辑</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.status === 0"
|
|
||||||
type="danger"
|
type="danger"
|
||||||
link
|
link
|
||||||
@click="handleCancel(row)"
|
@click="handleDelete(row)"
|
||||||
>取消</el-button>
|
v-if="canDeleteTask(row)"
|
||||||
|
>删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -771,15 +1045,32 @@ const getUserNames = (ids) => {
|
|||||||
>
|
>
|
||||||
<el-form ref="newTaskFormRef" :model="newTaskForm" :rules="rules" label-width="100px">
|
<el-form ref="newTaskFormRef" :model="newTaskForm" :rules="rules" label-width="100px">
|
||||||
<el-form-item label="关联计划" prop="plan_id">
|
<el-form-item label="关联计划" prop="plan_id">
|
||||||
<el-input v-model="newTaskForm.plan_id" placeholder="请输入计划ID" type="number" />
|
<el-select
|
||||||
|
v-model="newTaskForm.plan_id"
|
||||||
|
placeholder="请选择关联计划"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handlePlanChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="plan in planList"
|
||||||
|
:key="plan.id"
|
||||||
|
:label="plan.plan_name"
|
||||||
|
:value="plan.id"
|
||||||
|
>
|
||||||
|
<span>{{ plan.plan_name }}</span>
|
||||||
|
<span style="float: right; color: #8492a6; font-size: 13px">
|
||||||
|
{{ plan.plan_type === 'daily' ? '日常巡护' : '专项巡护' }}
|
||||||
|
</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="任务名称" prop="task_name">
|
<el-form-item label="任务名称" prop="task_name">
|
||||||
<el-input v-model="newTaskForm.task_name" placeholder="请输入任务名称" maxlength="100" show-word-limit />
|
<el-input v-model="newTaskForm.task_name" placeholder="请输入任务名称" maxlength="100" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="任务类型" prop="task_type">
|
<el-form-item label="任务类型" prop="task_type">
|
||||||
<el-radio-group v-model="newTaskForm.task_type">
|
<el-radio-group v-model="newTaskForm.task_type">
|
||||||
<el-radio :value="'regular'" label="常规">常规</el-radio>
|
<el-radio value="regular">常规</el-radio>
|
||||||
<el-radio :value="'emergency'" label="紧急">紧急</el-radio>
|
<el-radio value="emergency">紧急</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="巡护日期" prop="patrol_date">
|
<el-form-item label="巡护日期" prop="patrol_date">
|
||||||
@ -871,6 +1162,108 @@ const getUserNames = (ids) => {
|
|||||||
@current-change="handlePageChange"
|
@current-change="handlePageChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加编辑对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
width="600px"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item label="计划ID" prop="plan_id">
|
||||||
|
<el-input v-model="form.plan_id" placeholder="请输入计划ID" type="number" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="任务名称" prop="task_name">
|
||||||
|
<el-input v-model="form.task_name" placeholder="请输入任务名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="任务类型" prop="task_type">
|
||||||
|
<el-radio-group v-model="form.task_type">
|
||||||
|
<el-radio value="regular">常规</el-radio>
|
||||||
|
<el-radio value="emergency">紧急</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="巡护日期" prop="patrol_date">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="form.patrol_date"
|
||||||
|
type="date"
|
||||||
|
placeholder="选择巡护日期"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="执行时间" required>
|
||||||
|
<el-col :span="11">
|
||||||
|
<el-form-item prop="start_time">
|
||||||
|
<el-time-picker
|
||||||
|
v-model="form.start_time"
|
||||||
|
placeholder="开始时间"
|
||||||
|
format="HH:mm"
|
||||||
|
value-format="HH:mm"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="2" class="text-center">
|
||||||
|
<span class="text-gray-500">-</span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="11">
|
||||||
|
<el-form-item prop="end_time">
|
||||||
|
<el-time-picker
|
||||||
|
v-model="form.end_time"
|
||||||
|
placeholder="结束时间"
|
||||||
|
format="HH:mm"
|
||||||
|
value-format="HH:mm"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="执行人" prop="executor_ids">
|
||||||
|
<el-select
|
||||||
|
v-model="form.executor_ids"
|
||||||
|
multiple
|
||||||
|
placeholder="请选择执行人"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="user in userList"
|
||||||
|
:key="user.id"
|
||||||
|
:label="user.real_name || user.username"
|
||||||
|
:value="user.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="优先级" prop="priority">
|
||||||
|
<el-radio-group v-model="form.priority">
|
||||||
|
<el-radio :value="1">高</el-radio>
|
||||||
|
<el-radio :value="2">中</el-radio>
|
||||||
|
<el-radio :value="3">低</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="任务描述" prop="description">
|
||||||
|
<el-input
|
||||||
|
v-model="form.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="请输入任务描述"
|
||||||
|
maxlength="1000"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -1068,6 +1461,15 @@ const getUserNames = (ids) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.operation-btns {
|
||||||
|
padding: 16px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-20 {
|
.mt-20 {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -394,8 +394,8 @@ onMounted(() => {
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="图片" prop="image_url">
|
<el-form-item label="图片" prop="image_url">
|
||||||
<el-radio-group v-model="form.imageInputType" class="mb-4" @change="handleImageTypeChange">
|
<el-radio-group v-model="form.imageInputType" class="mb-4" @change="handleImageTypeChange">
|
||||||
<el-radio :value="'url'">输入图片地址</el-radio>
|
<el-radio value="url">输入图片地址</el-radio>
|
||||||
<el-radio :value="'upload'">上传图片</el-radio>
|
<el-radio value="upload">上传图片</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
|
||||||
<template v-if="form.imageInputType === 'url'">
|
<template v-if="form.imageInputType === 'url'">
|
||||||
|
|||||||
@ -193,8 +193,8 @@ const icons = {
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备份类型">
|
<el-form-item label="备份类型">
|
||||||
<el-radio-group v-model="backupConfig.backupType">
|
<el-radio-group v-model="backupConfig.backupType">
|
||||||
<el-radio label="full">完整备份</el-radio>
|
<el-radio value="full">完整备份</el-radio>
|
||||||
<el-radio label="data">数据备份</el-radio>
|
<el-radio value="data">数据备份</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="保留时间">
|
<el-form-item label="保留时间">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user