|
@@ -1,843 +1,277 @@
|
|
|
-<!--运维任务管理 - 窨井设备异常派发中心-->
|
|
|
|
|
|
|
+<!--运维任务管理 - 窨井设备待派单工单(内联复用 workOrder/list.vue 的列表,固定查 order_status=0 + rootTypeId=4)-->
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="terminal-dispatch">
|
|
|
|
|
- <!-- 页面头部 -->
|
|
|
|
|
- <div class="page-header">
|
|
|
|
|
- <div class="header-left">
|
|
|
|
|
- <el-icon><Warning /></el-icon>
|
|
|
|
|
- <span class="title">窨井设备异常派发中心</span>
|
|
|
|
|
- <el-tag type="danger" effect="dark">未处理异常 {{ abnormalCount }}</el-tag>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="header-right">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="searchKeyword"
|
|
|
|
|
- placeholder="搜索设备编号/名称/位置"
|
|
|
|
|
- clearable
|
|
|
|
|
- :prefix-icon="Search"
|
|
|
|
|
- style="width: 240px"
|
|
|
|
|
- @keyup.enter="currentPage = 1"
|
|
|
|
|
- @clear="currentPage = 1"
|
|
|
|
|
- />
|
|
|
|
|
- <el-select v-model="deviceTypeFilter" placeholder="设备类型" clearable style="width: 130px" @change="currentPage = 1">
|
|
|
|
|
- <el-option label="供水窨井" value="5" />
|
|
|
|
|
- <el-option label="排水窨井" value="9" />
|
|
|
|
|
- <el-option label="燃气窨井" value="10" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- <el-button type="primary" :icon="Refresh" @click="loadDevices" :loading="loading">刷新</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 统计卡片 -->
|
|
|
|
|
- <div class="stats-cards">
|
|
|
|
|
- <div class="stat-card critical">
|
|
|
|
|
- <div class="stat-value">{{ criticalCount }}</div>
|
|
|
|
|
- <div class="stat-label">严重异常</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="stat-card warning">
|
|
|
|
|
- <div class="stat-value">{{ warningCount }}</div>
|
|
|
|
|
- <div class="stat-label">一般异常</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="stat-card">
|
|
|
|
|
- <div class="stat-value">{{ pendingCount }}</div>
|
|
|
|
|
- <div class="stat-label">待派发</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="stat-card">
|
|
|
|
|
- <div class="stat-value">{{ dispatchedCount }}</div>
|
|
|
|
|
- <div class="stat-label">已派发</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 批量操作栏 -->
|
|
|
|
|
- <div class="batch-bar" v-if="selectedRows.length > 0">
|
|
|
|
|
- <span>已选择 <strong>{{ selectedRows.length }}</strong> 个异常设备</span>
|
|
|
|
|
- <el-button type="primary" @click="batchDispatch">批量提交工单</el-button>
|
|
|
|
|
- <el-button @click="clearSelection">取消选择</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 主内容区:左侧异常设备列表 + 右侧详情 -->
|
|
|
|
|
- <div class="main-layout">
|
|
|
|
|
- <!-- 左侧异常设备列表 -->
|
|
|
|
|
- <div class="terminal-list-panel">
|
|
|
|
|
- <div class="list-header">
|
|
|
|
|
- <span>异常设备列表({{ filteredTerminals.length }} 台)</span>
|
|
|
|
|
- <el-checkbox v-model="selectAll" @change="handleSelectAll">全选</el-checkbox>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="terminal-list" v-loading="loading">
|
|
|
|
|
- <div
|
|
|
|
|
- v-for="terminal in paginatedTerminals"
|
|
|
|
|
- :key="terminal.id"
|
|
|
|
|
- class="terminal-item"
|
|
|
|
|
- :class="{
|
|
|
|
|
- selected: selectedTerminal?.id === terminal.id,
|
|
|
|
|
- critical: terminal.abnormalLevel === 'critical',
|
|
|
|
|
- warning: terminal.abnormalLevel === 'warning'
|
|
|
|
|
- }"
|
|
|
|
|
- @click="selectTerminal(terminal)"
|
|
|
|
|
- >
|
|
|
|
|
- <el-checkbox
|
|
|
|
|
- :model-value="selectedRows.includes(terminal.id)"
|
|
|
|
|
- @change="(val) => toggleSelect(terminal.id, val)"
|
|
|
|
|
- class="select-checkbox"
|
|
|
|
|
- @click.stop
|
|
|
|
|
- />
|
|
|
|
|
- <div class="terminal-content">
|
|
|
|
|
- <div class="terminal-header">
|
|
|
|
|
- <span class="terminal-code">{{ terminal.code }}</span>
|
|
|
|
|
- <el-tag :type="terminal.abnormalLevel === 'critical' ? 'danger' : 'warning'" size="small">
|
|
|
|
|
- {{ terminal.abnormalLevel === 'critical' ? '严重' : '一般' }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="terminal-name">{{ terminal.name }}</div>
|
|
|
|
|
- <div class="terminal-location">
|
|
|
|
|
- <el-icon><Location /></el-icon> {{ terminal.locationShort }}
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="terminal-desc">{{ terminal.abnormalDesc }}</div>
|
|
|
|
|
- <div class="terminal-time">异常时间: {{ terminal.abnormalTime || '—' }}</div>
|
|
|
|
|
- <div class="terminal-footer">
|
|
|
|
|
- <el-tag v-if="terminal.workOrderSubmitted" type="success" size="small" effect="plain">已派发</el-tag>
|
|
|
|
|
- <el-tag v-else type="danger" size="small" effect="plain">待派发</el-tag>
|
|
|
|
|
- <span class="type-tag">{{ terminal.deviceTypeName }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-empty v-if="!loading && filteredTerminals.length === 0" description="暂无异常设备" :image-size="80" />
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="pagination-area" v-if="filteredTerminals.length > pageSize">
|
|
|
|
|
- <el-pagination
|
|
|
|
|
- small
|
|
|
|
|
- layout="prev, pager, next"
|
|
|
|
|
- :total="filteredTerminals.length"
|
|
|
|
|
- :page-size="pageSize"
|
|
|
|
|
- v-model:current-page="currentPage"
|
|
|
|
|
|
|
+ <div class="operation-task">
|
|
|
|
|
+ <!-- 搜索栏 -->
|
|
|
|
|
+ <el-card shadow="never" class="search-card">
|
|
|
|
|
+ <el-form :model="queryParams" inline class="search-form" @submit.prevent>
|
|
|
|
|
+ <el-form-item label="关键字">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="queryParams.keyword"
|
|
|
|
|
+ placeholder="工单编号 / 设备编码 / 故障描述"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 240px"
|
|
|
|
|
+ @keyup.enter="handleQuery"
|
|
|
/>
|
|
/>
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 右侧详情面板 -->
|
|
|
|
|
- <div class="detail-panel" v-if="selectedTerminal">
|
|
|
|
|
- <el-tabs v-model="activeTab" @tab-change="onTabChange">
|
|
|
|
|
- <!-- 设备信息 -->
|
|
|
|
|
- <el-tab-pane label="设备信息" name="info">
|
|
|
|
|
- <div class="info-section">
|
|
|
|
|
- <div class="section-title">
|
|
|
|
|
- <span><el-icon><Monitor /></el-icon> 设备基本信息</span>
|
|
|
|
|
- <el-button
|
|
|
|
|
- type="primary"
|
|
|
|
|
- size="small"
|
|
|
|
|
- @click="openDispatchDialog(selectedTerminal)"
|
|
|
|
|
- :disabled="selectedTerminal.workOrderSubmitted"
|
|
|
|
|
- >
|
|
|
|
|
- {{ selectedTerminal.workOrderSubmitted ? '已派发' : '提交工单' }}
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-descriptions :column="2" border>
|
|
|
|
|
- <el-descriptions-item label="设备编号">{{ selectedTerminal.code }}</el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="设备名称">{{ selectedTerminal.name }}</el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="设备类型">{{ selectedTerminal.deviceTypeName }}</el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="设备位置">{{ selectedTerminal.location }}</el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="异常级别">
|
|
|
|
|
- <el-tag :type="selectedTerminal.abnormalLevel === 'critical' ? 'danger' : 'warning'">
|
|
|
|
|
- {{ selectedTerminal.abnormalLevel === 'critical' ? '严重异常' : '一般异常' }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
- </el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="派发状态">
|
|
|
|
|
- <el-tag :type="selectedTerminal.workOrderSubmitted ? 'success' : 'danger'">
|
|
|
|
|
- {{ selectedTerminal.workOrderSubmitted ? '已派发' : '待派发' }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
- </el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="异常描述" :span="2">{{ selectedTerminal.abnormalDesc }}</el-descriptions-item>
|
|
|
|
|
- </el-descriptions>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 实时监测数据 -->
|
|
|
|
|
- <div class="info-section">
|
|
|
|
|
- <div class="section-title"><el-icon><DataAnalysis /></el-icon> 实时监测数据</div>
|
|
|
|
|
- <div class="monitor-grid">
|
|
|
|
|
- <div class="monitor-item" :class="{ alert: selectedTerminal.status.tilt }">
|
|
|
|
|
- <div class="monitor-label">倾斜角度</div>
|
|
|
|
|
- <div class="monitor-value">{{ selectedTerminal.tiltAngle }}°</div>
|
|
|
|
|
- <div class="monitor-extra">阈值: {{ selectedTerminal.threshold }}°</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="monitor-item" :class="{ alert: selectedTerminal.status.waterInfiltration }">
|
|
|
|
|
- <div class="monitor-label">水浸状态</div>
|
|
|
|
|
- <div class="monitor-value">{{ selectedTerminal.status.waterInfiltration ? '报警' : '正常' }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="monitor-item" :class="{ alert: selectedTerminal.status.waterLevel }">
|
|
|
|
|
- <div class="monitor-label">水位状态</div>
|
|
|
|
|
- <div class="monitor-value">{{ selectedTerminal.status.waterLevel ? '超标' : '正常' }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="monitor-item" :class="{ alert: selectedTerminal.status.lowBattery }">
|
|
|
|
|
- <div class="monitor-label">电量</div>
|
|
|
|
|
- <div class="monitor-value">{{ selectedTerminal.battery }}%</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="monitor-item" :class="{ alert: selectedTerminal.status.lowSignal }">
|
|
|
|
|
- <div class="monitor-label">信号强度</div>
|
|
|
|
|
- <div class="monitor-value">{{ selectedTerminal.signal }}%</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="monitor-item">
|
|
|
|
|
- <div class="monitor-label">温度</div>
|
|
|
|
|
- <div class="monitor-value">{{ selectedTerminal.temperature || '—' }}°C</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="last-upload">最后上传时间: {{ selectedTerminal.lastTime || '—' }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 位置信息 -->
|
|
|
|
|
- <div class="location-section">
|
|
|
|
|
- <div class="section-title"><el-icon><MapLocation /></el-icon> 设备位置信息</div>
|
|
|
|
|
- <div class="location-info">
|
|
|
|
|
- <el-icon :size="20" color="#f56c6c"><Location /></el-icon>
|
|
|
|
|
- <span>{{ selectedTerminal.location }}</span>
|
|
|
|
|
- <span v-if="selectedTerminal.lng" class="coords">(经度: {{ selectedTerminal.lng }}, 纬度: {{ selectedTerminal.lat }})</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-tab-pane>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 处置记录 -->
|
|
|
|
|
- <el-tab-pane label="处置记录" name="history">
|
|
|
|
|
- <div class="history-section">
|
|
|
|
|
- <div class="section-title"><el-icon><Document /></el-icon> 工单处置记录</div>
|
|
|
|
|
- <div v-loading="loadingOrders">
|
|
|
|
|
- <el-timeline v-if="workOrders.length">
|
|
|
|
|
- <el-timeline-item
|
|
|
|
|
- v-for="order in workOrders"
|
|
|
|
|
- :key="order.orderId"
|
|
|
|
|
- :timestamp="order.createTime || ''"
|
|
|
|
|
- :type="Number(order.orderStatus) >= 6 ? 'success' : 'primary'"
|
|
|
|
|
- >
|
|
|
|
|
- <div class="record-content">
|
|
|
|
|
- <div class="record-row"><strong>工单编号:</strong> {{ order.orderNo || '—' }}</div>
|
|
|
|
|
- <div class="record-row"><strong>优先级:</strong>
|
|
|
|
|
- <el-tag :type="orderLevelTagType(order.orderLevel)" size="small">{{ priorityText(order.orderLevel) }}</el-tag>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="record-row"><strong>状态:</strong>
|
|
|
|
|
- <el-tag :type="orderStatusTagType(order.orderStatus)" size="small">{{ orderStatusText(order.orderStatus) }}</el-tag>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="record-row" v-if="order.orderDesc"><strong>描述:</strong> {{ order.orderDesc }}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-timeline-item>
|
|
|
|
|
- </el-timeline>
|
|
|
|
|
- <el-empty v-else description="暂无工单处置记录" :image-size="80" />
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-tab-pane>
|
|
|
|
|
- </el-tabs>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 未选中状态 -->
|
|
|
|
|
- <div v-else class="empty-panel">
|
|
|
|
|
- <el-empty description="请从左侧选择异常设备查看详情" :image-size="140" />
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 提交工单对话框 -->
|
|
|
|
|
- <el-dialog v-model="dispatchDialogVisible" title="提交工单" width="520px" @close="dispatchForm = { orderLevel: defaultOrderLevel(), orderDesc: '', owner: '' }">
|
|
|
|
|
- <el-form :model="dispatchForm" label-width="100px">
|
|
|
|
|
- <el-form-item label="设备名称">
|
|
|
|
|
- <el-input :model-value="currentDispatchTerminal?.name" disabled />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="异常描述">
|
|
|
|
|
- <el-input type="textarea" :model-value="currentDispatchTerminal?.abnormalDesc" disabled :rows="2" />
|
|
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
-
|
|
|
|
|
- <el-form-item label="优先级" required>
|
|
|
|
|
- <el-select v-model="dispatchForm.orderLevel" placeholder="选择优先级" style="width: 100%">
|
|
|
|
|
- <el-option v-for="item in workOrderLevels" :key="item.value" :label="item.label" :value="Number(item.value)" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="问题描述" required>
|
|
|
|
|
- <el-input type="textarea" v-model="dispatchForm.orderDesc" placeholder="填写问题描述、维修要求等" :rows="3" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="负责人">
|
|
|
|
|
- <el-input v-model="dispatchForm.owner" placeholder="填写负责人姓名(选填)" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <template #footer>
|
|
|
|
|
- <el-button @click="dispatchDialogVisible = false">取消</el-button>
|
|
|
|
|
- <el-button type="primary" @click="confirmDispatch" :loading="submitting">确认提交</el-button>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 批量提交工单对话框 -->
|
|
|
|
|
- <el-dialog v-model="batchDispatchDialogVisible" title="批量提交工单" width="450px">
|
|
|
|
|
- <p>将向以下 {{ batchDispatchIds.length }} 个异常设备提交工单:</p>
|
|
|
|
|
- <div class="batch-list">
|
|
|
|
|
- <el-tag v-for="id in batchDispatchIds.slice(0, 5)" :key="id" size="small" style="margin: 4px">
|
|
|
|
|
- {{ getTerminalCode(id) }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
- <span v-if="batchDispatchIds.length > 5">等{{ batchDispatchIds.length }}个设备</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-form style="margin-top: 16px" label-width="80px">
|
|
|
|
|
<el-form-item label="优先级">
|
|
<el-form-item label="优先级">
|
|
|
- <el-select v-model="batchDispatchForm.orderLevel" style="width: 100%">
|
|
|
|
|
|
|
+ <el-select v-model="queryParams.orderLevel" placeholder="全部" clearable style="width: 120px">
|
|
|
<el-option v-for="item in workOrderLevels" :key="item.value" :label="item.label" :value="Number(item.value)" />
|
|
<el-option v-for="item in workOrderLevels" :key="item.value" :label="item.label" :value="Number(item.value)" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="问题描述">
|
|
|
|
|
- <el-input type="textarea" v-model="batchDispatchForm.orderDesc" :rows="2" placeholder="批量工单描述" />
|
|
|
|
|
|
|
+ <el-form-item label="故障时间">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="faultTimeRange"
|
|
|
|
|
+ type="datetimerange"
|
|
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
+ start-placeholder="开始时间"
|
|
|
|
|
+ end-placeholder="结束时间"
|
|
|
|
|
+ style="width: 360px"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
|
|
|
|
|
+ <el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
- <template #footer>
|
|
|
|
|
- <el-button @click="batchDispatchDialogVisible = false">取消</el-button>
|
|
|
|
|
- <el-button type="primary" @click="confirmBatchDispatch" :loading="submitting">确认批量提交</el-button>
|
|
|
|
|
- </template>
|
|
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-card shadow="never" class="table-card">
|
|
|
|
|
+ <el-table v-loading="loading" :data="list" border stripe>
|
|
|
|
|
+ <el-table-column label="工单编号" prop="orderNo" min-width="180" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="故障描述" min-width="240">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tooltip
|
|
|
|
|
+ :content="row.orderDesc"
|
|
|
|
|
+ placement="top"
|
|
|
|
|
+ popper-class="order-desc-tooltip"
|
|
|
|
|
+ :show-after="200"
|
|
|
|
|
+ :disabled="!row.orderDesc"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="desc-cell">{{ row.orderDesc || '-' }}</span>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="优先级" width="100" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag :type="orderLevelType(row.orderLevel)">{{ orderLevelText(row.orderLevel) }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="设备编码" prop="deviceCode" min-width="150" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">{{ row.deviceCode || '-' }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="处置人" prop="receiveUserName" min-width="100" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">{{ row.receiveUserName || '-' }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="故障时间" prop="faultTime" width="180" align="center">
|
|
|
|
|
+ <template #default="{ row }">{{ timeText(row.faultTime) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="计划完成时间" prop="planFinishTime" width="180" align="center">
|
|
|
|
|
+ <template #default="{ row }">{{ timeText(row.planFinishTime) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="状态" width="100" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag :type="orderStatusType(row.orderStatus)">{{ orderStatusText(row.orderStatus) }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="160" align="center" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button link type="primary" @click="goDispatch(row)">派单</el-button>
|
|
|
|
|
+ <el-button link type="primary" @click="openHistory(row)">历史处置</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <pagination
|
|
|
|
|
+ v-show="total > 0"
|
|
|
|
|
+ v-model:page="queryParams.pageNum"
|
|
|
|
|
+ v-model:limit="queryParams.pageSize"
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ @pagination="getList"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 历史处置记录对话框 -->
|
|
|
|
|
+ <el-dialog v-model="historyVisible" title="历史处置记录" width="760px" @close="historyList = []">
|
|
|
|
|
+ <el-table v-loading="historyLoading" :data="historyList" border stripe>
|
|
|
|
|
+ <el-table-column prop="orderNo" label="工单编号" min-width="180" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="故障描述" min-width="240">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="desc-cell">{{ row.orderDesc || '-' }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="faultTime" label="故障时间" width="180" align="center">
|
|
|
|
|
+ <template #default="{ row }">{{ timeText(row.faultTime) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <pagination
|
|
|
|
|
+ v-show="historyTotal > 0"
|
|
|
|
|
+ v-model:page="historyPageNum"
|
|
|
|
|
+ v-model:limit="historyPageSize"
|
|
|
|
|
+ :total="historyTotal"
|
|
|
|
|
+ @pagination="loadHistory"
|
|
|
|
|
+ />
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import { ref, computed, onMounted } from 'vue'
|
|
|
|
|
-import { Warning, Search, Refresh, Location, Monitor, MapLocation, Document, DataAnalysis } from '@element-plus/icons-vue'
|
|
|
|
|
-import { ElMessage } from 'element-plus'
|
|
|
|
|
-import { getManholeAlarmMonitorList, addManholeWorkOrder } from '@/api/pipeNetwork/basic'
|
|
|
|
|
-import { listWorkOrder } from '@/api/pipeNetwork/ops/workOrder'
|
|
|
|
|
|
|
+import { ref, reactive, onMounted } from 'vue'
|
|
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
|
|
+import { listWorkOrder } from '@/api/pipeNetwork/ops/workOrder.js'
|
|
|
|
|
+import {
|
|
|
|
|
+ orderStatusText,
|
|
|
|
|
+ orderStatusType,
|
|
|
|
|
+ timeText
|
|
|
|
|
+} from '@/views/subSystem/pipeNetwork/ops/workOrder/js/workOrderDict.js'
|
|
|
import { useWorkOrderLevelDict } from '@/views/subSystem/pipeNetwork/ops/workOrder/js/useWorkOrderLevelDict.js'
|
|
import { useWorkOrderLevelDict } from '@/views/subSystem/pipeNetwork/ops/workOrder/js/useWorkOrderLevelDict.js'
|
|
|
|
|
|
|
|
-const { workOrderLevels, defaultOrderLevel, orderLevelText: priorityText } = useWorkOrderLevelDict()
|
|
|
|
|
|
|
+defineOptions({ name: 'OperationTask' })
|
|
|
|
|
|
|
|
-// ==================== 常量 ====================
|
|
|
|
|
-const TYPE_MAP = { '5': '供水窨井', '9': '排水窨井', '10': '燃气窨井' }
|
|
|
|
|
-const LOW_BATTERY_THRESHOLD = 15
|
|
|
|
|
-const LOW_SIGNAL_THRESHOLD = 20
|
|
|
|
|
|
|
+const { workOrderLevels, orderLevelText, orderLevelType } = useWorkOrderLevelDict()
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
|
|
|
-// ==================== 状态 ====================
|
|
|
|
|
|
|
+// ==================== 列表(固定:待派单 order_status=0 + 窨井设备 rootTypeId=4)====================
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
|
-const loadingOrders = ref(false)
|
|
|
|
|
-const submitting = ref(false)
|
|
|
|
|
-const devices = ref([])
|
|
|
|
|
-const searchKeyword = ref('')
|
|
|
|
|
-const deviceTypeFilter = ref('')
|
|
|
|
|
-const currentPage = ref(1)
|
|
|
|
|
-const pageSize = 8
|
|
|
|
|
-
|
|
|
|
|
-const selectedRows = ref([])
|
|
|
|
|
-const selectAll = ref(false)
|
|
|
|
|
-const selectedTerminal = ref(null)
|
|
|
|
|
-const activeTab = ref('info')
|
|
|
|
|
-
|
|
|
|
|
-const workOrders = ref([])
|
|
|
|
|
-
|
|
|
|
|
-const dispatchDialogVisible = ref(false)
|
|
|
|
|
-const currentDispatchTerminal = ref(null)
|
|
|
|
|
-const dispatchForm = ref({ orderLevel: defaultOrderLevel(), orderDesc: '', owner: '' })
|
|
|
|
|
-
|
|
|
|
|
-const batchDispatchDialogVisible = ref(false)
|
|
|
|
|
-const batchDispatchIds = ref([])
|
|
|
|
|
-const batchDispatchForm = ref({ orderLevel: defaultOrderLevel(), orderDesc: '' })
|
|
|
|
|
|
|
+const list = ref([])
|
|
|
|
|
+const total = ref(0)
|
|
|
|
|
+const faultTimeRange = ref([])
|
|
|
|
|
+
|
|
|
|
|
+const queryParams = reactive({
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ keyword: '',
|
|
|
|
|
+ orderLevel: undefined,
|
|
|
|
|
+ faultTimeStart: '',
|
|
|
|
|
+ faultTimeEnd: ''
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
-// ==================== 数据加载 ====================
|
|
|
|
|
-async function loadDevices() {
|
|
|
|
|
- loading.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- const keyword = searchKeyword.value?.trim()
|
|
|
|
|
- const res = await getManholeAlarmMonitorList({ keyword: keyword || undefined })
|
|
|
|
|
- const result = res.data || {}
|
|
|
|
|
- const allDevices = (result.devices || []).map(item => normalizeDevice(item))
|
|
|
|
|
- // 只保留有异常的设备
|
|
|
|
|
- devices.value = allDevices.filter(d => d.hasAlert)
|
|
|
|
|
- // 默认选中第一个
|
|
|
|
|
- if (devices.value.length && !selectedTerminal.value) {
|
|
|
|
|
- selectedTerminal.value = devices.value[0]
|
|
|
|
|
- } else if (devices.value.length === 0) {
|
|
|
|
|
- selectedTerminal.value = null
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- devices.value = []
|
|
|
|
|
- const msg = error?.message || error?.msg || '未知错误'
|
|
|
|
|
- ElMessage.error('设备数据加载失败: ' + msg)
|
|
|
|
|
- } finally {
|
|
|
|
|
- loading.value = false
|
|
|
|
|
|
|
+/** 统一构造查询入参:固定查待派单 + 窨井设备,不随查询条件透出 */
|
|
|
|
|
+function buildQueryParams() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...queryParams,
|
|
|
|
|
+ orderStatus: '0',
|
|
|
|
|
+ rootTypeId: '4'
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function loadWorkOrders(deviceId) {
|
|
|
|
|
- if (!deviceId) return
|
|
|
|
|
- loadingOrders.value = true
|
|
|
|
|
|
|
+async function getList() {
|
|
|
|
|
+ loading.value = true
|
|
|
try {
|
|
try {
|
|
|
- const { data } = await listWorkOrder({ deviceId, pageNum: 1, pageSize: 20 })
|
|
|
|
|
- workOrders.value = data?.records || data?.rows || []
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- workOrders.value = []
|
|
|
|
|
- console.error('[operationTask] 工单记录加载失败:', error)
|
|
|
|
|
|
|
+ const res = await listWorkOrder(buildQueryParams())
|
|
|
|
|
+ const data = res.data || {}
|
|
|
|
|
+ list.value = data.records || []
|
|
|
|
|
+ total.value = data.total || 0
|
|
|
} finally {
|
|
} finally {
|
|
|
- loadingOrders.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// ==================== 设备数据规范化 ====================
|
|
|
|
|
-function normalizeDevice(raw) {
|
|
|
|
|
- const md = raw?.manholeData || {}
|
|
|
|
|
- const st = raw?.equipmentStatus || {}
|
|
|
|
|
- const typeId = raw?.equipmentTypeId || ''
|
|
|
|
|
-
|
|
|
|
|
- // 数值解析
|
|
|
|
|
- const tiltAngle = parseFloat(md.tiltAngle) || 0
|
|
|
|
|
- const angleThreshold = parseFloat(md.angleAlarmThreshold) || 15
|
|
|
|
|
- const battery = parseFloat(md.batteryLevel) || 0
|
|
|
|
|
- const signal = parseFloat(md.signalStrength) || 0
|
|
|
|
|
- const temperature = md.temperatureValue ? parseFloat(md.temperatureValue).toFixed(1) : null
|
|
|
|
|
- const isOnline = Number(st.onlineStatus) === 1
|
|
|
|
|
-
|
|
|
|
|
- // 异常判定
|
|
|
|
|
- const tilt = isOnline && tiltAngle > angleThreshold
|
|
|
|
|
- const waterInfiltration = isOnline && `${md.waterInfiltrationAlarmStatus}` === '1'
|
|
|
|
|
- const waterLevel = isOnline && `${md.waterLevelAlarmStatus}` === '1'
|
|
|
|
|
- const lowBattery = battery > 0 && battery <= LOW_BATTERY_THRESHOLD
|
|
|
|
|
- const lowSignal = signal > 0 && signal <= LOW_SIGNAL_THRESHOLD
|
|
|
|
|
-
|
|
|
|
|
- // 告警等级
|
|
|
|
|
- const isCritical = tilt || waterInfiltration || waterLevel
|
|
|
|
|
- const isWarning = !isCritical && (lowBattery || lowSignal)
|
|
|
|
|
- const hasAlert = isCritical || isWarning
|
|
|
|
|
- const alertLevel = isCritical ? 'critical' : (isWarning ? 'warning' : null)
|
|
|
|
|
-
|
|
|
|
|
- // 异常描述
|
|
|
|
|
- const parts = []
|
|
|
|
|
- if (tilt) parts.push(`倾斜角度${tiltAngle}°超过阈值${angleThreshold}°`)
|
|
|
|
|
- if (waterInfiltration) parts.push('水浸报警')
|
|
|
|
|
- if (waterLevel) parts.push('水位超标报警')
|
|
|
|
|
- if (lowBattery) parts.push(`电量仅剩${battery}%`)
|
|
|
|
|
- if (lowSignal) parts.push(`信号强度${signal}%`)
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- id: raw?.equipmentId || raw?.equipmentCode || '',
|
|
|
|
|
- code: raw?.equipmentCode || raw?.equipmentId || '',
|
|
|
|
|
- equipmentId: raw?.equipmentId || '',
|
|
|
|
|
- name: raw?.equipmentName || raw?.equipmentCode || '未命名设备',
|
|
|
|
|
- typeId,
|
|
|
|
|
- deviceTypeName: TYPE_MAP[typeId] || raw?.equipmentTypeName || '未知类型',
|
|
|
|
|
- location: raw?.equipmentLocation || '暂无位置信息',
|
|
|
|
|
- locationShort: (raw?.equipmentLocation || '暂无位置').slice(0, 20),
|
|
|
|
|
- lng: raw?.longitude ?? null,
|
|
|
|
|
- lat: raw?.latitude ?? null,
|
|
|
|
|
- battery,
|
|
|
|
|
- signal,
|
|
|
|
|
- temperature,
|
|
|
|
|
- tiltAngle,
|
|
|
|
|
- threshold: angleThreshold,
|
|
|
|
|
- status: { tilt, waterInfiltration, waterLevel, lowBattery, lowSignal },
|
|
|
|
|
- hasAlert,
|
|
|
|
|
- alertLevel,
|
|
|
|
|
- abnormalLevel: alertLevel,
|
|
|
|
|
- abnormalDesc: parts.join(';') || '设备状态正常',
|
|
|
|
|
- abnormalTime: md.uploadTime || md.createTime || '',
|
|
|
|
|
- lastTime: md.uploadTime || md.createTime || '',
|
|
|
|
|
- workOrderSubmitted: !!raw?.workOrderSubmitted
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// ==================== 计算属性 ====================
|
|
|
|
|
-const filteredTerminals = computed(() => {
|
|
|
|
|
- let list = devices.value
|
|
|
|
|
- if (searchKeyword.value) {
|
|
|
|
|
- const kw = searchKeyword.value.toLowerCase()
|
|
|
|
|
- list = list.filter(t =>
|
|
|
|
|
- t.code.toLowerCase().includes(kw) ||
|
|
|
|
|
- t.name.toLowerCase().includes(kw) ||
|
|
|
|
|
- t.location.toLowerCase().includes(kw)
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- if (deviceTypeFilter.value) {
|
|
|
|
|
- list = list.filter(t => t.typeId === deviceTypeFilter.value)
|
|
|
|
|
- }
|
|
|
|
|
- return list
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-const paginatedTerminals = computed(() => {
|
|
|
|
|
- const start = (currentPage.value - 1) * pageSize
|
|
|
|
|
- return filteredTerminals.value.slice(start, start + pageSize)
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-const abnormalCount = computed(() => devices.value.length)
|
|
|
|
|
-const criticalCount = computed(() => devices.value.filter(t => t.abnormalLevel === 'critical').length)
|
|
|
|
|
-const warningCount = computed(() => devices.value.filter(t => t.abnormalLevel === 'warning').length)
|
|
|
|
|
-const pendingCount = computed(() => devices.value.filter(t => !t.workOrderSubmitted).length)
|
|
|
|
|
-const dispatchedCount = computed(() => devices.value.filter(t => t.workOrderSubmitted).length)
|
|
|
|
|
-
|
|
|
|
|
-// ==================== 辅助函数 ====================
|
|
|
|
|
-const orderStatusText = (s) => ({
|
|
|
|
|
- 0: '待派单', 1: '待接单', 2: '已接单', 3: '处理中',
|
|
|
|
|
- 4: '待验收', 5: '已结案', 6: '延期中', 7: '已取消'
|
|
|
|
|
-}[s] || '—')
|
|
|
|
|
-const orderLevelTagType = (l) => ({ 0: 'danger', 1: 'warning', 2: 'info' }[l] || 'info')
|
|
|
|
|
-const orderStatusTagType = (s) => {
|
|
|
|
|
- const n = Number(s)
|
|
|
|
|
- if (n >= 6) return 'success'
|
|
|
|
|
- if (n >= 3) return 'primary'
|
|
|
|
|
- if (n >= 1) return 'warning'
|
|
|
|
|
- return 'info'
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// ==================== 选择操作 ====================
|
|
|
|
|
-const handleSelectAll = (val) => {
|
|
|
|
|
- if (val) {
|
|
|
|
|
- selectedRows.value = paginatedTerminals.value.map(t => t.id)
|
|
|
|
|
- } else {
|
|
|
|
|
- selectedRows.value = []
|
|
|
|
|
|
|
+ loading.value = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const toggleSelect = (id, val) => {
|
|
|
|
|
- if (val) {
|
|
|
|
|
- if (!selectedRows.value.includes(id)) selectedRows.value.push(id)
|
|
|
|
|
|
|
+function handleQuery() {
|
|
|
|
|
+ queryParams.pageNum = 1
|
|
|
|
|
+ if (faultTimeRange.value && faultTimeRange.value.length === 2) {
|
|
|
|
|
+ queryParams.faultTimeStart = faultTimeRange.value[0]
|
|
|
|
|
+ queryParams.faultTimeEnd = faultTimeRange.value[1]
|
|
|
} else {
|
|
} else {
|
|
|
- selectedRows.value = selectedRows.value.filter(i => i !== id)
|
|
|
|
|
- }
|
|
|
|
|
- selectAll.value = paginatedTerminals.value.length > 0 &&
|
|
|
|
|
- paginatedTerminals.value.every(t => selectedRows.value.includes(t.id))
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const clearSelection = () => {
|
|
|
|
|
- selectedRows.value = []
|
|
|
|
|
- selectAll.value = false
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const selectTerminal = (terminal) => {
|
|
|
|
|
- selectedTerminal.value = terminal
|
|
|
|
|
- if (activeTab.value === 'history') {
|
|
|
|
|
- loadWorkOrders(terminal.equipmentId)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const onTabChange = (tabName) => {
|
|
|
|
|
- if (tabName === 'history' && selectedTerminal.value) {
|
|
|
|
|
- loadWorkOrders(selectedTerminal.value.equipmentId)
|
|
|
|
|
|
|
+ queryParams.faultTimeStart = ''
|
|
|
|
|
+ queryParams.faultTimeEnd = ''
|
|
|
}
|
|
}
|
|
|
|
|
+ getList()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const getTerminalCode = (id) => {
|
|
|
|
|
- const terminal = devices.value.find(t => t.id === id)
|
|
|
|
|
- return terminal?.code || id
|
|
|
|
|
|
|
+function resetQuery() {
|
|
|
|
|
+ queryParams.keyword = ''
|
|
|
|
|
+ queryParams.orderLevel = undefined
|
|
|
|
|
+ faultTimeRange.value = []
|
|
|
|
|
+ queryParams.faultTimeStart = ''
|
|
|
|
|
+ queryParams.faultTimeEnd = ''
|
|
|
|
|
+ handleQuery()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ==================== 提交工单 ====================
|
|
|
|
|
-const openDispatchDialog = (terminal) => {
|
|
|
|
|
- currentDispatchTerminal.value = terminal
|
|
|
|
|
- dispatchForm.value = {
|
|
|
|
|
- orderLevel: terminal.abnormalLevel === 'critical' && workOrderLevels.value.some(item => Number(item.value) === 0) ? 0 : defaultOrderLevel(),
|
|
|
|
|
- orderDesc: terminal.abnormalDesc || '',
|
|
|
|
|
- owner: ''
|
|
|
|
|
- }
|
|
|
|
|
- dispatchDialogVisible.value = true
|
|
|
|
|
-}
|
|
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ getList()
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
-const confirmDispatch = async () => {
|
|
|
|
|
- if (!dispatchForm.value.orderDesc?.trim()) {
|
|
|
|
|
- ElMessage.warning('请填写问题描述')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- if (!currentDispatchTerminal.value?.equipmentId) {
|
|
|
|
|
- ElMessage.warning('设备信息缺失,无法提交工单')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- submitting.value = true
|
|
|
|
|
|
|
+// ==================== 历史处置 ====================
|
|
|
|
|
+const historyVisible = ref(false)
|
|
|
|
|
+const historyLoading = ref(false)
|
|
|
|
|
+const historyList = ref([])
|
|
|
|
|
+const historyTotal = ref(0)
|
|
|
|
|
+const historyPageNum = ref(1)
|
|
|
|
|
+const historyPageSize = ref(20)
|
|
|
|
|
+const currentDeviceId = ref('')
|
|
|
|
|
+
|
|
|
|
|
+async function openHistory(row) {
|
|
|
|
|
+ currentDeviceId.value = row.deviceId
|
|
|
|
|
+ historyPageNum.value = 1
|
|
|
|
|
+ historyVisible.value = true
|
|
|
|
|
+ await loadHistory()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadHistory() {
|
|
|
|
|
+ if (!currentDeviceId.value) return
|
|
|
|
|
+ historyLoading.value = true
|
|
|
try {
|
|
try {
|
|
|
- const terminal = currentDispatchTerminal.value
|
|
|
|
|
- const desc = dispatchForm.value.owner
|
|
|
|
|
- ? `${dispatchForm.value.orderDesc}(负责人:${dispatchForm.value.owner})`
|
|
|
|
|
- : dispatchForm.value.orderDesc
|
|
|
|
|
- await addManholeWorkOrder({
|
|
|
|
|
- deviceId: terminal.equipmentId,
|
|
|
|
|
- orderLevel: dispatchForm.value.orderLevel,
|
|
|
|
|
- orderDesc: desc
|
|
|
|
|
|
|
+ const res = await listWorkOrder({
|
|
|
|
|
+ deviceId: currentDeviceId.value,
|
|
|
|
|
+ pageNum: historyPageNum.value,
|
|
|
|
|
+ pageSize: historyPageSize.value
|
|
|
})
|
|
})
|
|
|
- // 标记已派发
|
|
|
|
|
- terminal.workOrderSubmitted = true
|
|
|
|
|
- ElMessage.success('工单已提交,相关部门将即刻处理')
|
|
|
|
|
- dispatchDialogVisible.value = false
|
|
|
|
|
- // 如果在处置记录tab,刷新工单列表
|
|
|
|
|
- if (activeTab.value === 'history') {
|
|
|
|
|
- loadWorkOrders(terminal.equipmentId)
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- const msg = error?.message || error?.msg || '未知错误'
|
|
|
|
|
- ElMessage.error('工单提交失败: ' + msg)
|
|
|
|
|
|
|
+ const data = res.data || {}
|
|
|
|
|
+ historyList.value = data.records || []
|
|
|
|
|
+ historyTotal.value = data.total || 0
|
|
|
} finally {
|
|
} finally {
|
|
|
- submitting.value = false
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// ==================== 批量提交工单 ====================
|
|
|
|
|
-const batchDispatch = () => {
|
|
|
|
|
- if (selectedRows.value.length === 0) {
|
|
|
|
|
- ElMessage.warning('请先选择异常设备')
|
|
|
|
|
- return
|
|
|
|
|
|
|
+ historyLoading.value = false
|
|
|
}
|
|
}
|
|
|
- batchDispatchIds.value = [...selectedRows.value]
|
|
|
|
|
- batchDispatchForm.value = { orderLevel: defaultOrderLevel(), orderDesc: '' }
|
|
|
|
|
- batchDispatchDialogVisible.value = true
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const confirmBatchDispatch = async () => {
|
|
|
|
|
- if (!batchDispatchForm.value.orderDesc?.trim()) {
|
|
|
|
|
- ElMessage.warning('请填写问题描述')
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- submitting.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- let successCount = 0
|
|
|
|
|
- let failCount = 0
|
|
|
|
|
- for (const id of batchDispatchIds.value) {
|
|
|
|
|
- const terminal = devices.value.find(t => t.id === id)
|
|
|
|
|
- if (!terminal || terminal.workOrderSubmitted) continue
|
|
|
|
|
- try {
|
|
|
|
|
- await addManholeWorkOrder({
|
|
|
|
|
- deviceId: terminal.equipmentId,
|
|
|
|
|
- orderLevel: batchDispatchForm.value.orderLevel,
|
|
|
|
|
- orderDesc: batchDispatchForm.value.orderDesc
|
|
|
|
|
- })
|
|
|
|
|
- terminal.workOrderSubmitted = true
|
|
|
|
|
- successCount++
|
|
|
|
|
- } catch {
|
|
|
|
|
- failCount++
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (successCount > 0) {
|
|
|
|
|
- ElMessage.success(`成功提交 ${successCount} 个工单${failCount > 0 ? `,${failCount}个失败` : ''}`)
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.error('批量提交工单全部失败')
|
|
|
|
|
- }
|
|
|
|
|
- batchDispatchDialogVisible.value = false
|
|
|
|
|
- clearSelection()
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- const msg = error?.message || error?.msg || '未知错误'
|
|
|
|
|
- ElMessage.error('批量提交失败: ' + msg)
|
|
|
|
|
- } finally {
|
|
|
|
|
- submitting.value = false
|
|
|
|
|
- }
|
|
|
|
|
|
|
+// ==================== 派单:按工单号跳转到本子系统(manholeCover)的工单处置页 ====================
|
|
|
|
|
+function goDispatch(row) {
|
|
|
|
|
+ router.push({
|
|
|
|
|
+ path: '/subSystem/manholeCover/caseHandling/workOrderDispose',
|
|
|
|
|
+ query: { keyword: row.orderNo || '', rootTypeId: '4' }
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-// ==================== 生命周期 ====================
|
|
|
|
|
-onMounted(() => {
|
|
|
|
|
- loadDevices()
|
|
|
|
|
-})
|
|
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
-<style scoped>
|
|
|
|
|
-.terminal-dispatch {
|
|
|
|
|
- background: #f0f2f6;
|
|
|
|
|
- min-height: 100vh;
|
|
|
|
|
- padding: 20px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.page-header {
|
|
|
|
|
- background: white;
|
|
|
|
|
- border-radius: 16px;
|
|
|
|
|
- padding: 16px 24px;
|
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.header-left {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.operation-task {
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ min-height: calc(100vh - 84px);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.header-left .title {
|
|
|
|
|
- font-size: 20px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: #1f2d3d;
|
|
|
|
|
|
|
+.search-card {
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.header-right {
|
|
|
|
|
|
|
+/* 查询栏:flex + row-gap 控制换行行间距,清零 el-form-item 默认 margin-bottom */
|
|
|
|
|
+.search-form {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- gap: 12px;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
flex-wrap: wrap;
|
|
flex-wrap: wrap;
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.stats-cards {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- gap: 16px;
|
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.stat-card {
|
|
|
|
|
- background: white;
|
|
|
|
|
- border-radius: 12px;
|
|
|
|
|
- padding: 12px 24px;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
- min-width: 110px;
|
|
|
|
|
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ row-gap: 4px;
|
|
|
|
|
|
|
|
-.stat-card.critical .stat-value { color: #f56c6c; }
|
|
|
|
|
-.stat-card.warning .stat-value { color: #e6a23c; }
|
|
|
|
|
-.stat-value { font-size: 28px; font-weight: bold; }
|
|
|
|
|
-.stat-label { font-size: 13px; color: #606266; margin-top: 4px; }
|
|
|
|
|
-
|
|
|
|
|
-.batch-bar {
|
|
|
|
|
- background: #ecf5ff;
|
|
|
|
|
- border-radius: 12px;
|
|
|
|
|
- padding: 12px 20px;
|
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 16px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.main-layout {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- gap: 20px;
|
|
|
|
|
|
|
+ :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.terminal-list-panel {
|
|
|
|
|
- width: 420px;
|
|
|
|
|
- background: white;
|
|
|
|
|
- border-radius: 16px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
|
|
+/* 单元格内省略:交给自定义 tooltip 展示全文 */
|
|
|
|
|
+.desc-cell {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ max-width: 100%;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ vertical-align: bottom;
|
|
|
}
|
|
}
|
|
|
|
|
+</style>
|
|
|
|
|
|
|
|
-.list-header {
|
|
|
|
|
- padding: 16px;
|
|
|
|
|
- border-bottom: 1px solid #e9ecef;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.terminal-list {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- overflow-y: auto;
|
|
|
|
|
- padding: 12px;
|
|
|
|
|
- max-height: calc(100vh - 280px);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.terminal-item {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: flex-start;
|
|
|
|
|
- gap: 10px;
|
|
|
|
|
- padding: 12px;
|
|
|
|
|
- margin-bottom: 10px;
|
|
|
|
|
- border-radius: 12px;
|
|
|
|
|
- background: #fafbfc;
|
|
|
|
|
- border: 1px solid #e9ecef;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- transition: all 0.2s;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.terminal-item.selected {
|
|
|
|
|
- background: #ecf5ff;
|
|
|
|
|
- border-color: #409eff;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.terminal-item.critical { border-left: 3px solid #f56c6c; }
|
|
|
|
|
-.terminal-item.warning { border-left: 3px solid #e6a23c; }
|
|
|
|
|
-
|
|
|
|
|
-.select-checkbox { margin-top: 2px; }
|
|
|
|
|
-.terminal-content { flex: 1; }
|
|
|
|
|
-
|
|
|
|
|
-.terminal-header {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- margin-bottom: 4px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.terminal-code { font-weight: 600; font-size: 14px; }
|
|
|
|
|
-.terminal-name { font-size: 13px; color: #303133; margin-bottom: 4px; }
|
|
|
|
|
-.terminal-location { font-size: 12px; color: #6c757d; display: flex; align-items: center; gap: 4px; margin-bottom: 4px; }
|
|
|
|
|
-.terminal-desc { font-size: 12px; color: #f56c6c; margin-bottom: 4px; }
|
|
|
|
|
-.terminal-time { font-size: 11px; color: #aaa; }
|
|
|
|
|
-
|
|
|
|
|
-.terminal-footer {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- margin-top: 6px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.type-tag {
|
|
|
|
|
- font-size: 11px;
|
|
|
|
|
- color: #909399;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.detail-panel {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- background: white;
|
|
|
|
|
- border-radius: 16px;
|
|
|
|
|
- padding: 20px;
|
|
|
|
|
- overflow-y: auto;
|
|
|
|
|
- max-height: calc(100vh - 160px);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.empty-panel {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- background: white;
|
|
|
|
|
- border-radius: 16px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.section-title {
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- margin: 16px 0 12px 0;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.info-section { margin-bottom: 20px; }
|
|
|
|
|
-
|
|
|
|
|
-.monitor-grid {
|
|
|
|
|
- display: grid;
|
|
|
|
|
- grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.monitor-item {
|
|
|
|
|
- background: #f5f7fa;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- padding: 12px;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
- border: 1px solid #e9ecef;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.monitor-item.alert {
|
|
|
|
|
- background: #fef0f0;
|
|
|
|
|
- border-color: #f56c6c;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.monitor-label { font-size: 12px; color: #909399; margin-bottom: 4px; }
|
|
|
|
|
-.monitor-value { font-size: 18px; font-weight: 600; color: #303133; }
|
|
|
|
|
-.monitor-item.alert .monitor-value { color: #f56c6c; }
|
|
|
|
|
-.monitor-extra { font-size: 11px; color: #aaa; margin-top: 2px; }
|
|
|
|
|
-
|
|
|
|
|
-.last-upload {
|
|
|
|
|
- margin-top: 10px;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- color: #909399;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.location-section { margin-bottom: 20px; }
|
|
|
|
|
-
|
|
|
|
|
-.location-info {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- background: #f5f7fa;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- padding: 16px;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- color: #303133;
|
|
|
|
|
|
|
+<style lang="scss">
|
|
|
|
|
+/* 故障描述悬浮提示:popper 挂在 body 下不受 scoped 限制,单独用全局样式块 */
|
|
|
|
|
+.el-popper.order-desc-tooltip {
|
|
|
|
|
+ max-width: 420px;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+ white-space: normal;
|
|
|
|
|
+ word-break: break-all;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-.location-info .coords { color: #909399; font-size: 12px; }
|
|
|
|
|
-
|
|
|
|
|
-.record-content { font-size: 13px; }
|
|
|
|
|
-.record-row { margin-bottom: 4px; }
|
|
|
|
|
-
|
|
|
|
|
-.pagination-area { padding: 12px; text-align: center; border-top: 1px solid #eee; }
|
|
|
|
|
-
|
|
|
|
|
-.batch-list { max-height: 150px; overflow-y: auto; }
|
|
|
|
|
</style>
|
|
</style>
|