Files
water-management-system/frontend/src/views/dispatch-command/CommandList.vue
T
bot_dev2 6c6db59ba9 feat(wm-production): #69 调度指令管理完整实现
- 实体: DispatchCommand/DispatchExecution/DispatchTracking
- Mapper: MyBatis-Plus + XML (含台账分页/详情/统计)
- Service: 完整状态机 (draft→issued→received→executing→completed/rejected)
- Controller: /api/production/dispatch-command (全生命周期API)
- SQL DDL: 三表+索引
- 前端: CommandList/CommandDetail/CommandCreate (Vue3+TS+Element Plus)
- 单元测试: DispatchCommandServiceTest + DispatchTrackingServiceTest
2026-06-14 15:30:52 +08:00

194 lines
9.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="command-list">
<el-card shadow="never" class="filter-card">
<el-form :inline="true" :model="filterForm">
<el-form-item label="关键词">
<el-input v-model="filterForm.keyword" placeholder="编号/标题" clearable @clear="handleSearch" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="filterForm.status" placeholder="全部" clearable @change="handleSearch">
<el-option label="草稿" value="draft" />
<el-option label="已下发" value="issued" />
<el-option label="已接收" value="received" />
<el-option label="执行中" value="executing" />
<el-option label="已完成" value="completed" />
<el-option label="已驳回" value="rejected" />
</el-select>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="filterForm.commandType" placeholder="全部" clearable @change="handleSearch">
<el-option label="常规" value="normal" />
<el-option label="应急" value="emergency" />
<el-option label="维护" value="maintenance" />
<el-option label="巡检" value="inspection" />
</el-select>
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker v-model="dateRange" type="daterange" range-separator="至"
start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD"
@change="handleSearch" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch"><el-icon><Search /></el-icon> 查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-row :gutter="12" style="margin-top: 12px">
<el-col :span="4" v-for="stat in stats" :key="stat.status">
<el-card shadow="hover" class="stat-card" @click="filterByStatus(stat.status)">
<div class="stat-value">{{ stat.count }}</div>
<div class="stat-label">{{ statusLabel(stat.status) }}</div>
</el-card>
</el-col>
</el-row>
<div style="margin-top: 16px; display: flex; justify-content: space-between; align-items: center">
<el-button type="primary" @click="showCreateDialog = true"><el-icon><Plus /></el-icon> 创建指令</el-button>
<el-button @click="handleBatchIssue" :disabled="!selectedIds.length">批量下发</el-button>
</div>
<el-table :data="tableData" border style="margin-top: 10px"
@selection-change="handleSelectionChange" v-loading="loading">
<el-table-column type="selection" width="50" />
<el-table-column prop="command_no" label="指令编号" width="220" />
<el-table-column prop="command_title" label="标题" min-width="200" show-overflow-tooltip />
<el-table-column prop="command_type" label="类型" width="80">
<template #default="{ row }">
<el-tag :type="typeTag(row.command_type)" size="small">{{ typeLabel(row.command_type) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="priority" label="优先级" width="80">
<template #default="{ row }">
<el-tag :type="priorityTag(row.priority)" size="small">{{ priorityLabel(row.priority) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="90">
<template #default="{ row }">
<el-tag :type="statusTag(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="执行进度" width="120">
<template #default="{ row }">
<span>{{ row.completed_count || 0 }}/{{ row.total_executions || 0 }}</span>
</template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="170" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="viewDetail(row)">详情</el-button>
<el-button link type="success" v-if="row.status === 'draft'" @click="handleIssue(row)">下发</el-button>
<el-button link type="danger" v-if="row.status === 'draft'" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin-top: 16px; justify-content: flex-end"
v-model:current-page="pagination.page" v-model:page-size="pagination.size"
:total="pagination.total" :page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next" @change="fetchData" />
<CommandCreate v-model:visible="showCreateDialog" @created="handleCreated" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus } from '@element-plus/icons-vue'
import { listCommands, issueCommand, getCommandStats } from '@/api/dispatchCommand'
import CommandCreate from './CommandCreate.vue'
const router = useRouter()
const loading = ref(false)
const tableData = ref<any[]>([])
const stats = ref<any[]>([])
const selectedIds = ref<number[]>([])
const showCreateDialog = ref(false)
const dateRange = ref<[string, string] | null>(null)
const filterForm = reactive({ keyword: '', status: '', commandType: '' })
const pagination = reactive({ page: 1, size: 10, total: 0 })
const statusMap: Record<string, { label: string; type: string }> = {
draft: { label: '草稿', type: 'info' },
issued: { label: '已下发', type: 'warning' },
received: { label: '已接收', type: '' },
executing: { label: '执行中', type: 'primary' },
completed: { label: '已完成', type: 'success' },
rejected: { label: '已驳回', type: 'danger' }
}
const typeMap: Record<string, string> = { normal: '常规', emergency: '应急', maintenance: '维护', inspection: '巡检' }
const priorityMap: Record<string, { label: string; type: string }> = {
low: { label: '低', type: 'info' }, normal: { label: '普通', type: '' },
high: { label: '高', type: 'warning' }, urgent: { label: '紧急', type: 'danger' }
}
function statusLabel(s: string) { return statusMap[s]?.label || s }
function statusTag(s: string) { return (statusMap[s]?.type || 'info') as any }
function typeLabel(t: string) { return typeMap[t] || t }
function typeTag(t: string) { return t === 'emergency' ? 'danger' : t === 'maintenance' ? 'warning' : '' }
function priorityLabel(p: string) { return priorityMap[p]?.label || p }
function priorityTag(p: string) { return (priorityMap[p]?.type || 'info') as any }
async function fetchData() {
loading.value = true
try {
const res = await listCommands({
page: pagination.page, size: pagination.size,
status: filterForm.status || undefined,
commandType: filterForm.commandType || undefined,
keyword: filterForm.keyword || undefined,
startDate: dateRange.value?.[0], endDate: dateRange.value?.[1]
})
tableData.value = res.data?.records || []
pagination.total = res.data?.total || 0
} finally { loading.value = false }
}
async function fetchStats() {
try { const res = await getCommandStats(); stats.value = res.data || [] } catch { /* ignore */ }
}
function handleSearch() { pagination.page = 1; fetchData() }
function handleReset() {
filterForm.keyword = ''; filterForm.status = ''; filterForm.commandType = ''; dateRange.value = null; handleSearch()
}
function filterByStatus(status: string) { filterForm.status = status; handleSearch() }
function handleSelectionChange(rows: any[]) { selectedIds.value = rows.map((r: any) => r.id) }
function viewDetail(row: any) { router.push({ path: `/dispatch-command/${row.id}` }) }
async function handleIssue(row: any) {
try {
await ElMessageBox.confirm(`确认下发指令 "${row.command_title}" ?`, '下发确认')
await issueCommand(row.id, 1, 'admin'); ElMessage.success('指令已下发'); fetchData(); fetchStats()
} catch { /* cancel */ }
}
async function handleBatchIssue() {
try {
await ElMessageBox.confirm(`确认批量下发 ${selectedIds.value.length} 条指令?`, '批量下发')
for (const id of selectedIds.value) { await issueCommand(id, 1, 'admin') }
ElMessage.success('批量下发完成'); fetchData(); fetchStats()
} catch { /* cancel */ }
}
function handleDelete(row: any) {
ElMessageBox.confirm(`确认删除指令 "${row.command_title}" ?`, '删除确认', { type: 'warning' })
.then(() => { ElMessage.info('删除功能待实现(逻辑删除)') }).catch(() => { /* cancel */ })
}
function handleCreated() { showCreateDialog.value = false; fetchData(); fetchStats() }
onMounted(() => { fetchData(); fetchStats() })
</script>
<style scoped>
.filter-card :deep(.el-form-item) { margin-bottom: 0; }
.stat-card { cursor: pointer; text-align: center; }
.stat-value { font-size: 28px; font-weight: bold; color: #409eff; }
.stat-label { font-size: 13px; color: #909399; margin-top: 4px; }
</style>