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() {
|
||||
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
|
||||
* @returns {Promise} 返回删除结果
|
||||
*/
|
||||
@ -100,6 +107,13 @@ export function deletePlan(id) {
|
||||
|
||||
/**
|
||||
* 批量删除巡护计划
|
||||
* 只能删除以下状态的计划:
|
||||
* - 未开始的计划
|
||||
* - 已完成的计划
|
||||
* - 已取消的计划
|
||||
* 不能删除:
|
||||
* - 进行中的计划
|
||||
* - 已有关联巡护任务的计划
|
||||
* @param {Array<string|number>} ids - 计划ID列表
|
||||
* @returns {Promise} 返回批量删除结果
|
||||
*/
|
||||
|
||||
@ -90,4 +90,36 @@ export function getExecutorTasks(executorId, params = {}) {
|
||||
*/
|
||||
export function cancelTask(id, 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>
|
||||
<el-menu-item index="/monitor/species">物种监测</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 index="patrol">
|
||||
|
||||
@ -59,6 +59,11 @@ const router = createRouter({
|
||||
name: 'EnvironmentMonitor',
|
||||
component: () => import('../views/monitor/environment/index.vue')
|
||||
},
|
||||
{
|
||||
path: 'monitor/observations',
|
||||
name: 'ObservationsMonitor',
|
||||
component: () => import('../views/monitor/observations/index.vue')
|
||||
},
|
||||
{
|
||||
path: 'patrol/tasks',
|
||||
name: 'PatrolTasks',
|
||||
|
||||
@ -452,8 +452,8 @@ onMounted(() => {
|
||||
</el-form-item>
|
||||
<el-form-item label="导出格式">
|
||||
<el-radio-group v-model="exportForm.format">
|
||||
<el-radio label="excel">Excel</el-radio>
|
||||
<el-radio label="csv">CSV</el-radio>
|
||||
<el-radio value="excel">Excel</el-radio>
|
||||
<el-radio value="csv">CSV</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</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>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Refresh, Plus, Download } from '@element-plus/icons-vue'
|
||||
import { getEventList, updateEventStatus, createEvent, getEventDetail, getEventStatistics, exportEvents, batchExportEvents } from '@/api/patrol/event'
|
||||
import { Search, Refresh, Plus, Download, Delete } from '@element-plus/icons-vue'
|
||||
import { getEventList, updateEventStatus, createEvent, getEventDetail, getEventStatistics, exportEvents, batchExportEvents, deleteEvent, batchDeleteEvents } from '@/api/patrol/event'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
|
||||
// 表格数据
|
||||
@ -15,7 +15,8 @@ const statistics = ref({
|
||||
pending: 0,
|
||||
processing: 0,
|
||||
processed: 0,
|
||||
closed: 0
|
||||
closed: 0,
|
||||
avgHandlingTime: 0
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
@ -102,15 +103,15 @@ const getList = async () => {
|
||||
})
|
||||
|
||||
if (res.success) {
|
||||
tableData.value = res.data.list
|
||||
// 将数据数组反转为正序
|
||||
tableData.value = [...res.data.list].reverse()
|
||||
total.value = Number(res.data.pagination.total)
|
||||
currentPage.value = Number(res.data.pagination.page || 1)
|
||||
pageSize.value = Number(res.data.pagination.page_size || 10)
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取安防事件列表错误:', error)
|
||||
console.error('获取事件列表错误:', error)
|
||||
ElMessage.error('获取列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
@ -122,10 +123,23 @@ const getStatistics = async () => {
|
||||
try {
|
||||
const res = await getEventStatistics()
|
||||
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) {
|
||||
console.error('获取统计数据错误:', error)
|
||||
ElMessage.error('获取统计数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
@ -367,6 +381,81 @@ const handleSelectionChange = (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(() => {
|
||||
getList()
|
||||
getStatistics()
|
||||
@ -437,6 +526,7 @@ onMounted(() => {
|
||||
<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="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>
|
||||
</div>
|
||||
</div>
|
||||
@ -568,6 +658,12 @@ onMounted(() => {
|
||||
link
|
||||
@click="handleClose(row)"
|
||||
>关闭</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(row)"
|
||||
v-if="row.handling_status === 2 || row.handling_status === 3"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
<div class="card-header">
|
||||
<span class="header-title">巡护计划管理</span>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -126,7 +127,7 @@
|
||||
|
||||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||
<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
|
||||
link
|
||||
type="primary"
|
||||
@ -134,7 +135,14 @@
|
||||
>
|
||||
{{ row.status === 1 ? '禁用' : '启用' }}
|
||||
</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>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -155,25 +163,26 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 新建计划弹窗 -->
|
||||
<!-- 巡护计划表单弹窗 -->
|
||||
<el-dialog
|
||||
v-model="newPlanDialogVisible"
|
||||
title="新建巡护计划"
|
||||
v-model="planDialogVisible"
|
||||
:title="isEdit ? '编辑巡护计划' : '新建巡护计划'"
|
||||
width="600px"
|
||||
@close="handleDialogClose"
|
||||
>
|
||||
<el-form
|
||||
ref="newPlanFormRef"
|
||||
:model="newPlanForm"
|
||||
ref="planFormRef"
|
||||
:model="planForm"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
class="plan-form"
|
||||
>
|
||||
<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 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="special">专项巡护</el-radio>
|
||||
</el-radio-group>
|
||||
@ -183,7 +192,7 @@
|
||||
<el-col :span="11">
|
||||
<el-form-item prop="start_date">
|
||||
<el-date-picker
|
||||
v-model="newPlanForm.start_date"
|
||||
v-model="planForm.start_date"
|
||||
type="date"
|
||||
placeholder="开始日期"
|
||||
style="width: 100%"
|
||||
@ -197,7 +206,7 @@
|
||||
<el-col :span="11">
|
||||
<el-form-item prop="end_date">
|
||||
<el-date-picker
|
||||
v-model="newPlanForm.end_date"
|
||||
v-model="planForm.end_date"
|
||||
type="date"
|
||||
placeholder="结束日期"
|
||||
style="width: 100%"
|
||||
@ -208,7 +217,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<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="weekly">每周</el-radio>
|
||||
<el-radio value="monthly">每月</el-radio>
|
||||
@ -259,7 +268,7 @@
|
||||
|
||||
<el-form-item label="计划描述" prop="description">
|
||||
<el-input
|
||||
v-model="newPlanForm.description"
|
||||
v-model="planForm.description"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入计划描述"
|
||||
@ -267,7 +276,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<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="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
@ -275,74 +284,15 @@
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="newPlanDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleNewPlanSubmit">确定</el-button>
|
||||
<el-button @click="planDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handlePlanSubmit">确定</el-button>
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Plus, Delete, Refresh } from '@element-plus/icons-vue'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
@ -375,7 +325,10 @@ const dateRange = ref(['', ''])
|
||||
|
||||
// 新建计划相关
|
||||
const newPlanDialogVisible = ref(false)
|
||||
const newPlanForm = reactive({
|
||||
const planDialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const planForm = reactive({
|
||||
id: '',
|
||||
plan_name: '',
|
||||
plan_type: 'daily',
|
||||
description: '',
|
||||
@ -386,7 +339,7 @@ const newPlanForm = reactive({
|
||||
status: 1
|
||||
})
|
||||
|
||||
const newPlanFormRef = ref()
|
||||
const planFormRef = ref()
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
@ -400,7 +353,7 @@ const rules = {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await checkPlanName(value, newPlanForm.id)
|
||||
const res = await checkPlanName(value, planForm.id)
|
||||
if (res.success && !res.data.available) {
|
||||
callback(new Error('计划名称已存在'))
|
||||
} else {
|
||||
@ -420,7 +373,7 @@ const rules = {
|
||||
{ required: true, message: '请选择结束日期', trigger: 'change' },
|
||||
{
|
||||
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('结束日期必须大于开始日期'))
|
||||
} else {
|
||||
callback()
|
||||
@ -473,7 +426,7 @@ const getList = async () => {
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(newPlanForm, {
|
||||
Object.assign(planForm, {
|
||||
plan_name: '',
|
||||
plan_type: 'daily',
|
||||
description: '',
|
||||
@ -484,45 +437,71 @@ const resetForm = () => {
|
||||
status: 1
|
||||
})
|
||||
addressList.value = [] // 清空地址列表
|
||||
if (newPlanFormRef.value) {
|
||||
newPlanFormRef.value.resetFields()
|
||||
if (planFormRef.value) {
|
||||
planFormRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理新建计划提交
|
||||
const handleNewPlanSubmit = async () => {
|
||||
if (!newPlanFormRef.value) return
|
||||
const handlePlanSubmit = async () => {
|
||||
if (!planFormRef.value) return
|
||||
|
||||
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 = {
|
||||
plan_name: newPlanForm.plan_name.trim(),
|
||||
plan_type: newPlanForm.plan_type,
|
||||
description: newPlanForm.description?.trim(),
|
||||
start_date: newPlanForm.start_date,
|
||||
end_date: newPlanForm.end_date,
|
||||
area_scope: newPlanForm.area_scope,
|
||||
task_frequency: newPlanForm.task_frequency,
|
||||
status: newPlanForm.status
|
||||
plan_name: planForm.plan_name.trim(),
|
||||
plan_type: planForm.plan_type,
|
||||
description: planForm.description?.trim(),
|
||||
start_date: planForm.start_date,
|
||||
end_date: planForm.end_date,
|
||||
area_scope: formattedAreaScope,
|
||||
task_frequency: planForm.task_frequency,
|
||||
status: planForm.status
|
||||
}
|
||||
|
||||
const res = await createPlan(submitData)
|
||||
if (res.success) {
|
||||
ElMessage.success('创建成功')
|
||||
newPlanDialogVisible.value = false
|
||||
getList()
|
||||
if (isEdit.value) {
|
||||
const res = await updatePlan(planForm.id, submitData)
|
||||
if (res.success) {
|
||||
ElMessage.success('更新成功')
|
||||
planDialogVisible.value = false
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '更新失败')
|
||||
}
|
||||
} 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) {
|
||||
console.error('创建计划错误:', error)
|
||||
ElMessage.error('创建失败,请检查输入是否正确')
|
||||
console.error('提交计划错误:', error)
|
||||
ElMessage.error('提交失败,请检查输入是否正确')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理关闭弹窗
|
||||
// 处理新建按钮点击
|
||||
const handleCreate = () => {
|
||||
isEdit.value = false
|
||||
resetForm()
|
||||
planDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 处理对话框关闭
|
||||
const handleDialogClose = () => {
|
||||
isEdit.value = false
|
||||
resetForm()
|
||||
}
|
||||
|
||||
@ -554,26 +533,6 @@ const handleSizeChange = (size) => {
|
||||
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) => {
|
||||
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) => {
|
||||
ElMessageBox.confirm('确定要删除该计划吗?', '提示', {
|
||||
// 先检查计划是否可删除
|
||||
if (!canDeletePlan(row)) {
|
||||
ElMessage.warning('该巡护计划已有关联的巡护任务或正在进行中,不能删除')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm('确认删除该巡护计划吗?此操作不可恢复', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
@ -607,41 +590,62 @@ const handleDelete = (row) => {
|
||||
ElMessage.error(res.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除计划错误:', error)
|
||||
ElMessage.error('删除失败')
|
||||
console.error('删除巡护计划错误:', error)
|
||||
// 根据错误响应给出具体提示
|
||||
if (error.response?.status === 400) {
|
||||
ElMessage.warning('该巡护计划已有关联的巡护任务或正在进行中,不能删除')
|
||||
} else {
|
||||
ElMessage.error('删除失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 批量删除计划
|
||||
// 处理批量删除
|
||||
const handleBatchDelete = () => {
|
||||
if (selectedPlans.value.length === 0) {
|
||||
ElMessage.warning('请选择要删除的计划')
|
||||
// 过滤出可删除的计划
|
||||
const deletableRows = selectedRows.value.filter(row => canDeletePlan(row))
|
||||
|
||||
if (deletableRows.length === 0) {
|
||||
ElMessage.warning('选中的计划中没有可删除的计划(不能删除进行中或已有关联任务的计划)')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(`确定要删除选中的 ${selectedPlans.value.length} 个计划吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
ElMessageBox.confirm(
|
||||
`确认删除选中的 ${deletableRows.length} 个计划吗?此操作不可恢复`,
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(async () => {
|
||||
try {
|
||||
const ids = selectedPlans.value.map(plan => plan.id)
|
||||
const res = await batchDeletePlans(ids)
|
||||
const res = await batchDeletePlans(deletableRows.map(row => row.id))
|
||||
if (res.success) {
|
||||
ElMessage.success('批量删除成功')
|
||||
selectedPlans.value = []
|
||||
getList()
|
||||
// 清空选择
|
||||
selectedRows.value = []
|
||||
} else {
|
||||
ElMessage.error(res.message || '批量删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量删除计划错误:', error)
|
||||
ElMessage.error('批量删除失败')
|
||||
console.error('批量删除巡护计划错误:', error)
|
||||
if (error.response?.status === 400) {
|
||||
ElMessage.warning('部分计划已有关联的巡护任务或正在进行中,不能删除')
|
||||
} else {
|
||||
ElMessage.error('批量删除失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 处理选择变化
|
||||
const handleSelectionChange = (selection) => {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
|
||||
// 添加刷新方法
|
||||
const handleRefresh = () => {
|
||||
getList()
|
||||
@ -685,7 +689,7 @@ const handleAddAddress = async () => {
|
||||
const result = await convertAddressToCoords(addressInput.value.trim())
|
||||
if (result) {
|
||||
addressList.value.push(result)
|
||||
newPlanForm.area_scope = addressList.value.map(item => ({
|
||||
planForm.area_scope = addressList.value.map(item => ({
|
||||
longitude: item.longitude,
|
||||
latitude: item.latitude
|
||||
}))
|
||||
@ -696,18 +700,60 @@ const handleAddAddress = async () => {
|
||||
// 删除地址
|
||||
const handleRemoveAddress = (index) => {
|
||||
addressList.value.splice(index, 1)
|
||||
newPlanForm.area_scope = addressList.value.map(item => ({
|
||||
planForm.area_scope = addressList.value.map(item => ({
|
||||
longitude: item.longitude,
|
||||
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(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "../../../styles/variables.scss" as *;
|
||||
@use "sass:color";
|
||||
|
||||
.patrol-plans {
|
||||
padding: 16px;
|
||||
background-color: #f0f2f5;
|
||||
@ -901,21 +947,283 @@ onMounted(() => {
|
||||
|
||||
.address-input-container {
|
||||
.address-input {
|
||||
margin-bottom: 12px;
|
||||
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-bottom: 12px;
|
||||
max-height: 240px;
|
||||
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: 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>
|
||||
@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from "vue";
|
||||
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 { getRoutePointsWithAddress } from '@/utils/map';
|
||||
import {
|
||||
@ -9,9 +9,12 @@ import {
|
||||
createTask,
|
||||
getTaskDetail,
|
||||
updateTask,
|
||||
cancelTask
|
||||
cancelTask,
|
||||
deleteTask,
|
||||
batchDeleteTasks
|
||||
} from '@/api/patrol/task';
|
||||
import { getUserList } from '@/api/user';
|
||||
import { getPlanList, getPlanDetail } from '@/api/patrol/plan';
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([]);
|
||||
@ -20,6 +23,12 @@ const loading = ref(false);
|
||||
// 用户列表数据
|
||||
const userList = ref([]);
|
||||
|
||||
// 选中的行
|
||||
const selectedRows = ref([]);
|
||||
|
||||
// 计划列表数据
|
||||
const planList = ref([]);
|
||||
|
||||
// 获取任务列表
|
||||
const getList = async () => {
|
||||
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 map = {
|
||||
@ -117,7 +142,23 @@ const handleNewTaskSubmit = async () => {
|
||||
try {
|
||||
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 = {
|
||||
plan_id: Number(newTaskForm.plan_id),
|
||||
task_name: newTaskForm.task_name.trim(),
|
||||
@ -125,11 +166,11 @@ const handleNewTaskSubmit = async () => {
|
||||
patrol_date: newTaskForm.patrol_date,
|
||||
start_time: newTaskForm.start_time,
|
||||
end_time: newTaskForm.end_time,
|
||||
route_points: newTaskForm.route_points,
|
||||
route_points: newTaskForm.route_points || [],
|
||||
executor_ids: newTaskForm.executor_ids,
|
||||
description: newTaskForm.description?.trim(),
|
||||
priority: newTaskForm.priority,
|
||||
status: newTaskForm.status
|
||||
description: newTaskForm.description?.trim() || '',
|
||||
priority: Number(newTaskForm.priority),
|
||||
status: 0
|
||||
};
|
||||
|
||||
const res = await createTask(submitData);
|
||||
@ -225,6 +266,7 @@ const resetFilters = () => {
|
||||
|
||||
onMounted(() => {
|
||||
getUserData(); // 获取用户列表
|
||||
getPlanData(); // 获取计划列表
|
||||
getList();
|
||||
});
|
||||
|
||||
@ -444,6 +486,233 @@ const getUserNames = (ids) => {
|
||||
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>
|
||||
|
||||
<template>
|
||||
@ -554,16 +823,23 @@ const getUserNames = (ids) => {
|
||||
</div>
|
||||
<div class="right">
|
||||
<el-button @click="resetFilters">重置</el-button>
|
||||
<el-button type="primary" @click="handleNewTask">新建任务</el-button>
|
||||
</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
|
||||
v-loading="loading"
|
||||
: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 prop="plan_id" label="计划ID" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
@ -620,21 +896,19 @@ const getUserNames = (ids) => {
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
</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 }">
|
||||
<el-button type="primary" link @click="handleView(row)">查看</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 0"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleEdit(row)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 0"
|
||||
type="danger"
|
||||
link
|
||||
@click="handleCancel(row)"
|
||||
>取消</el-button>
|
||||
@click="handleDelete(row)"
|
||||
v-if="canDeleteTask(row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -771,15 +1045,32 @@ const getUserNames = (ids) => {
|
||||
>
|
||||
<el-form ref="newTaskFormRef" :model="newTaskForm" :rules="rules" label-width="100px">
|
||||
<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 label="任务名称" prop="task_name">
|
||||
<el-input v-model="newTaskForm.task_name" placeholder="请输入任务名称" maxlength="100" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="任务类型" prop="task_type">
|
||||
<el-radio-group v-model="newTaskForm.task_type">
|
||||
<el-radio :value="'regular'" label="常规">常规</el-radio>
|
||||
<el-radio :value="'emergency'" label="紧急">紧急</el-radio>
|
||||
<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">
|
||||
@ -871,6 +1162,108 @@ const getUserNames = (ids) => {
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</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>
|
||||
</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 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@ -394,8 +394,8 @@ onMounted(() => {
|
||||
</el-form-item>
|
||||
<el-form-item label="图片" prop="image_url">
|
||||
<el-radio-group v-model="form.imageInputType" class="mb-4" @change="handleImageTypeChange">
|
||||
<el-radio :value="'url'">输入图片地址</el-radio>
|
||||
<el-radio :value="'upload'">上传图片</el-radio>
|
||||
<el-radio value="url">输入图片地址</el-radio>
|
||||
<el-radio value="upload">上传图片</el-radio>
|
||||
</el-radio-group>
|
||||
|
||||
<template v-if="form.imageInputType === 'url'">
|
||||
|
||||
@ -193,8 +193,8 @@ const icons = {
|
||||
</el-form-item>
|
||||
<el-form-item label="备份类型">
|
||||
<el-radio-group v-model="backupConfig.backupType">
|
||||
<el-radio label="full">完整备份</el-radio>
|
||||
<el-radio label="data">数据备份</el-radio>
|
||||
<el-radio value="full">完整备份</el-radio>
|
||||
<el-radio value="data">数据备份</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="保留时间">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user