|
@@ -1,27 +1,29 @@
|
|
|
-<!--运维任务管理-->
|
|
|
|
|
|
|
+<!--运维任务管理 - 窨井设备异常派发中心-->
|
|
|
<template>
|
|
<template>
|
|
|
<div class="terminal-dispatch">
|
|
<div class="terminal-dispatch">
|
|
|
<!-- 页面头部 -->
|
|
<!-- 页面头部 -->
|
|
|
<div class="page-header">
|
|
<div class="page-header">
|
|
|
<div class="header-left">
|
|
<div class="header-left">
|
|
|
<el-icon><Warning /></el-icon>
|
|
<el-icon><Warning /></el-icon>
|
|
|
- <span class="title">异常终端派发中心</span>
|
|
|
|
|
|
|
+ <span class="title">窨井设备异常派发中心</span>
|
|
|
<el-tag type="danger" effect="dark">未处理异常 {{ abnormalCount }}</el-tag>
|
|
<el-tag type="danger" effect="dark">未处理异常 {{ abnormalCount }}</el-tag>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="header-right">
|
|
<div class="header-right">
|
|
|
<el-input
|
|
<el-input
|
|
|
- v-model="searchKeyword"
|
|
|
|
|
- placeholder="搜索终端编号/名称/地址"
|
|
|
|
|
- clearable
|
|
|
|
|
- :prefix-icon="Search"
|
|
|
|
|
- style="width: 240px"
|
|
|
|
|
|
|
+ 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: 120px">
|
|
|
|
|
- <el-option label="智能井盖" value="smart" />
|
|
|
|
|
- <el-option label="防爆井盖" value="explosion" />
|
|
|
|
|
- <el-option label="水位计" value="water" />
|
|
|
|
|
|
|
+ <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-select>
|
|
|
- <el-button type="primary" :icon="Refresh" @click="refreshList">刷新</el-button>
|
|
|
|
|
|
|
+ <el-button type="primary" :icon="Refresh" @click="loadDevices" :loading="loading">刷新</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -40,141 +42,184 @@
|
|
|
<div class="stat-label">待派发</div>
|
|
<div class="stat-label">待派发</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="stat-card">
|
|
<div class="stat-card">
|
|
|
- <div class="stat-value">{{ todayDispatched }}</div>
|
|
|
|
|
- <div class="stat-label">今日已派发</div>
|
|
|
|
|
|
|
+ <div class="stat-value">{{ dispatchedCount }}</div>
|
|
|
|
|
+ <div class="stat-label">已派发</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 批量操作栏 -->
|
|
<!-- 批量操作栏 -->
|
|
|
<div class="batch-bar" v-if="selectedRows.length > 0">
|
|
<div class="batch-bar" v-if="selectedRows.length > 0">
|
|
|
- <span>已选择 <strong>{{ selectedRows.length }}</strong> 个异常终端</span>
|
|
|
|
|
- <el-button type="primary" @click="batchDispatch">批量派发</el-button>
|
|
|
|
|
|
|
+ <span>已选择 <strong>{{ selectedRows.length }}</strong> 个异常设备</span>
|
|
|
|
|
+ <el-button type="primary" @click="batchDispatch">批量提交工单</el-button>
|
|
|
<el-button @click="clearSelection">取消选择</el-button>
|
|
<el-button @click="clearSelection">取消选择</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 主内容区:左侧终端列表 + 右侧详情 -->
|
|
|
|
|
|
|
+ <!-- 主内容区:左侧异常设备列表 + 右侧详情 -->
|
|
|
<div class="main-layout">
|
|
<div class="main-layout">
|
|
|
- <!-- 左侧异常终端列表 -->
|
|
|
|
|
|
|
+ <!-- 左侧异常设备列表 -->
|
|
|
<div class="terminal-list-panel">
|
|
<div class="terminal-list-panel">
|
|
|
<div class="list-header">
|
|
<div class="list-header">
|
|
|
- <span>异常终端列表(未处理)</span>
|
|
|
|
|
|
|
+ <span>异常设备列表({{ filteredTerminals.length }} 台)</span>
|
|
|
<el-checkbox v-model="selectAll" @change="handleSelectAll">全选</el-checkbox>
|
|
<el-checkbox v-model="selectAll" @change="handleSelectAll">全选</el-checkbox>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="terminal-list">
|
|
|
|
|
|
|
+ <div class="terminal-list" v-loading="loading">
|
|
|
<div
|
|
<div
|
|
|
- v-for="terminal in paginatedTerminals"
|
|
|
|
|
- :key="terminal.id"
|
|
|
|
|
- class="terminal-item"
|
|
|
|
|
- :class="{
|
|
|
|
|
- selected: selectedRows.includes(terminal.id),
|
|
|
|
|
|
|
+ v-for="terminal in paginatedTerminals"
|
|
|
|
|
+ :key="terminal.id"
|
|
|
|
|
+ class="terminal-item"
|
|
|
|
|
+ :class="{
|
|
|
|
|
+ selected: selectedTerminal?.id === terminal.id,
|
|
|
critical: terminal.abnormalLevel === 'critical',
|
|
critical: terminal.abnormalLevel === 'critical',
|
|
|
warning: terminal.abnormalLevel === 'warning'
|
|
warning: terminal.abnormalLevel === 'warning'
|
|
|
}"
|
|
}"
|
|
|
|
|
+ @click="selectTerminal(terminal)"
|
|
|
>
|
|
>
|
|
|
<el-checkbox
|
|
<el-checkbox
|
|
|
- :model-value="selectedRows.includes(terminal.id)"
|
|
|
|
|
- @change="(val) => toggleSelect(terminal.id, val)"
|
|
|
|
|
- class="select-checkbox"
|
|
|
|
|
|
|
+ :model-value="selectedRows.includes(terminal.id)"
|
|
|
|
|
+ @change="(val) => toggleSelect(terminal.id, val)"
|
|
|
|
|
+ class="select-checkbox"
|
|
|
|
|
+ @click.stop
|
|
|
/>
|
|
/>
|
|
|
- <div class="terminal-content" @click="selectTerminal(terminal)">
|
|
|
|
|
|
|
+ <div class="terminal-content">
|
|
|
<div class="terminal-header">
|
|
<div class="terminal-header">
|
|
|
<span class="terminal-code">{{ terminal.code }}</span>
|
|
<span class="terminal-code">{{ terminal.code }}</span>
|
|
|
<el-tag :type="terminal.abnormalLevel === 'critical' ? 'danger' : 'warning'" size="small">
|
|
<el-tag :type="terminal.abnormalLevel === 'critical' ? 'danger' : 'warning'" size="small">
|
|
|
{{ terminal.abnormalLevel === 'critical' ? '严重' : '一般' }}
|
|
{{ terminal.abnormalLevel === 'critical' ? '严重' : '一般' }}
|
|
|
</el-tag>
|
|
</el-tag>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="terminal-name">{{ terminal.name }}</div>
|
|
|
<div class="terminal-location">
|
|
<div class="terminal-location">
|
|
|
<el-icon><Location /></el-icon> {{ terminal.locationShort }}
|
|
<el-icon><Location /></el-icon> {{ terminal.locationShort }}
|
|
|
</div>
|
|
</div>
|
|
|
<div class="terminal-desc">{{ terminal.abnormalDesc }}</div>
|
|
<div class="terminal-desc">{{ terminal.abnormalDesc }}</div>
|
|
|
- <div class="terminal-time">异常时间: {{ terminal.abnormalTime }}</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>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <el-empty v-if="!loading && filteredTerminals.length === 0" description="暂无异常设备" :image-size="80" />
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="pagination-area">
|
|
|
|
|
|
|
+ <div class="pagination-area" v-if="filteredTerminals.length > pageSize">
|
|
|
<el-pagination
|
|
<el-pagination
|
|
|
- small
|
|
|
|
|
- layout="prev, pager, next"
|
|
|
|
|
- :total="filteredTerminals.length"
|
|
|
|
|
- :page-size="pageSize"
|
|
|
|
|
- v-model:current-page="currentPage"
|
|
|
|
|
|
|
+ small
|
|
|
|
|
+ layout="prev, pager, next"
|
|
|
|
|
+ :total="filteredTerminals.length"
|
|
|
|
|
+ :page-size="pageSize"
|
|
|
|
|
+ v-model:current-page="currentPage"
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 右侧详情面板 -->
|
|
<!-- 右侧详情面板 -->
|
|
|
<div class="detail-panel" v-if="selectedTerminal">
|
|
<div class="detail-panel" v-if="selectedTerminal">
|
|
|
- <el-tabs v-model="activeTab">
|
|
|
|
|
- <!-- 终端基本信息与位置 -->
|
|
|
|
|
- <el-tab-pane label="终端信息" name="info">
|
|
|
|
|
|
|
+ <el-tabs v-model="activeTab" @tab-change="onTabChange">
|
|
|
|
|
+ <!-- 设备信息 -->
|
|
|
|
|
+ <el-tab-pane label="设备信息" name="info">
|
|
|
<div class="info-section">
|
|
<div class="info-section">
|
|
|
<div class="section-title">
|
|
<div class="section-title">
|
|
|
- <span><el-icon><Monitor /></el-icon> 终端基本信息</span>
|
|
|
|
|
- <el-button type="primary" size="small" @click="openDispatchDialog(selectedTerminal)">派发任务</el-button>
|
|
|
|
|
|
|
+ <span><el-icon><Monitor /></el-icon> 设备基本信息</span>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @click="openDispatchDialog(selectedTerminal)"
|
|
|
|
|
+ :disabled="selectedTerminal.workOrderSubmitted"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ selectedTerminal.workOrderSubmitted ? '已派发' : '提交工单' }}
|
|
|
|
|
+ </el-button>
|
|
|
</div>
|
|
</div>
|
|
|
<el-descriptions :column="2" border>
|
|
<el-descriptions :column="2" border>
|
|
|
- <el-descriptions-item label="终端编号">{{ selectedTerminal.code }}</el-descriptions-item>
|
|
|
|
|
|
|
+ <el-descriptions-item label="设备编号">{{ selectedTerminal.code }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="设备名称">{{ selectedTerminal.name }}</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.deviceTypeName }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="设备型号">{{ selectedTerminal.model }}</el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="权属单位">{{ selectedTerminal.ownerUnit }}</el-descriptions-item>
|
|
|
|
|
|
|
+ <el-descriptions-item label="设备位置">{{ selectedTerminal.location }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="异常级别">
|
|
<el-descriptions-item label="异常级别">
|
|
|
<el-tag :type="selectedTerminal.abnormalLevel === 'critical' ? 'danger' : 'warning'">
|
|
<el-tag :type="selectedTerminal.abnormalLevel === 'critical' ? 'danger' : 'warning'">
|
|
|
{{ selectedTerminal.abnormalLevel === 'critical' ? '严重异常' : '一般异常' }}
|
|
{{ selectedTerminal.abnormalLevel === 'critical' ? '严重异常' : '一般异常' }}
|
|
|
</el-tag>
|
|
</el-tag>
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="异常描述" :span="2">{{ selectedTerminal.abnormalDesc }}</el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="异常时间">{{ selectedTerminal.abnormalTime }}</el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="当前状态">
|
|
|
|
|
- <el-tag :type="selectedTerminal.status === 'pending' ? 'danger' : 'success'">
|
|
|
|
|
- {{ selectedTerminal.status === 'pending' ? '待处理' : '处理中' }}
|
|
|
|
|
|
|
+ <el-descriptions-item label="派发状态">
|
|
|
|
|
+ <el-tag :type="selectedTerminal.workOrderSubmitted ? 'success' : 'danger'">
|
|
|
|
|
+ {{ selectedTerminal.workOrderSubmitted ? '已派发' : '待派发' }}
|
|
|
</el-tag>
|
|
</el-tag>
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="异常描述" :span="2">{{ selectedTerminal.abnormalDesc }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
</el-descriptions>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 位置信息 - GIS地图模拟 -->
|
|
|
|
|
- <div class="location-section">
|
|
|
|
|
- <div class="section-title"><el-icon><MapLocation /></el-icon> 终端位置信息</div>
|
|
|
|
|
- <div class="map-container">
|
|
|
|
|
- <div class="mock-map">
|
|
|
|
|
- <el-icon :size="40" color="#f56c6c"><Location /></el-icon>
|
|
|
|
|
- <div class="map-coords">
|
|
|
|
|
- <div><strong>{{ selectedTerminal.address }}</strong></div>
|
|
|
|
|
- <div>经度: {{ selectedTerminal.lng }}, 纬度: {{ selectedTerminal.lat }}</div>
|
|
|
|
|
- </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>
|
|
</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>
|
|
</div>
|
|
|
</el-tab-pane>
|
|
</el-tab-pane>
|
|
|
|
|
|
|
|
- <!-- 历次处置记录 -->
|
|
|
|
|
|
|
+ <!-- 处置记录 -->
|
|
|
<el-tab-pane label="处置记录" name="history">
|
|
<el-tab-pane label="处置记录" name="history">
|
|
|
<div class="history-section">
|
|
<div class="history-section">
|
|
|
- <div class="section-title"><el-icon><Document /></el-icon> 历次处置信息</div>
|
|
|
|
|
- <el-timeline v-if="selectedTerminal.historyRecords?.length">
|
|
|
|
|
- <el-timeline-item
|
|
|
|
|
- v-for="record in selectedTerminal.historyRecords"
|
|
|
|
|
- :key="record.id"
|
|
|
|
|
- :timestamp="record.time"
|
|
|
|
|
- :type="record.type"
|
|
|
|
|
- >
|
|
|
|
|
- <div class="record-content">
|
|
|
|
|
- <div><strong>派发对象:</strong> {{ record.dispatchedTo }}</div>
|
|
|
|
|
- <div><strong>处置说明:</strong> {{ record.description }}</div>
|
|
|
|
|
- <div v-if="record.feedbackImages?.length" class="record-images">
|
|
|
|
|
- <el-image
|
|
|
|
|
- v-for="(img, idx) in record.feedbackImages"
|
|
|
|
|
- :key="idx"
|
|
|
|
|
- :src="img"
|
|
|
|
|
- fit="cover"
|
|
|
|
|
- class="history-img"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <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> {{ orderTypeText(order.orderType) }}</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>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
- </el-timeline-item>
|
|
|
|
|
- </el-timeline>
|
|
|
|
|
- <el-empty v-else description="暂无历史处置记录" :image-size="80" />
|
|
|
|
|
|
|
+ </el-timeline-item>
|
|
|
|
|
+ </el-timeline>
|
|
|
|
|
+ <el-empty v-else description="暂无工单处置记录" :image-size="80" />
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</el-tab-pane>
|
|
</el-tab-pane>
|
|
|
</el-tabs>
|
|
</el-tabs>
|
|
@@ -182,185 +227,242 @@
|
|
|
|
|
|
|
|
<!-- 未选中状态 -->
|
|
<!-- 未选中状态 -->
|
|
|
<div v-else class="empty-panel">
|
|
<div v-else class="empty-panel">
|
|
|
- <el-empty description="请从左侧选择异常终端查看详情" :image-size="140" />
|
|
|
|
|
|
|
+ <el-empty description="请从左侧选择异常设备查看详情" :image-size="140" />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 派发对话框 -->
|
|
|
|
|
- <el-dialog v-model="dispatchDialogVisible" :title="`派发任务 - ${currentDispatchTerminal?.code}`" width="500px">
|
|
|
|
|
|
|
+ <!-- 提交工单对话框 -->
|
|
|
|
|
+ <el-dialog v-model="dispatchDialogVisible" title="提交工单" width="520px" @close="dispatchForm = { orderType: 1, orderLevel: 2, orderDesc: '', owner: '' }">
|
|
|
<el-form :model="dispatchForm" label-width="100px">
|
|
<el-form :model="dispatchForm" label-width="100px">
|
|
|
- <el-form-item label="派发对象" required>
|
|
|
|
|
- <el-select v-model="dispatchForm.targetType" placeholder="选择派发类型" style="width: 100%" @change="onTargetTypeChange">
|
|
|
|
|
- <el-option label="施工单位" value="construction" />
|
|
|
|
|
- <el-option label="维护人员" value="maintainer" />
|
|
|
|
|
|
|
+ <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 label="工单类型" required>
|
|
|
|
|
+ <el-select v-model="dispatchForm.orderType" placeholder="选择工单类型" style="width: 100%">
|
|
|
|
|
+ <el-option label="故障维修" :value="1" />
|
|
|
|
|
+ <el-option label="日常巡检" :value="2" />
|
|
|
|
|
+ <el-option label="设备保养" :value="3" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="选择单位/人员" required>
|
|
|
|
|
- <el-select v-model="dispatchForm.targetId" placeholder="请选择" style="width: 100%">
|
|
|
|
|
- <el-option
|
|
|
|
|
- v-for="item in dispatchTargets"
|
|
|
|
|
- :key="item.id"
|
|
|
|
|
- :label="item.name"
|
|
|
|
|
- :value="item.id"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <el-form-item label="优先级" required>
|
|
|
|
|
+ <el-select v-model="dispatchForm.orderLevel" placeholder="选择优先级" style="width: 100%">
|
|
|
|
|
+ <el-option label="紧急" :value="1" />
|
|
|
|
|
+ <el-option label="一般" :value="2" />
|
|
|
|
|
+ <el-option label="低" :value="3" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="派发说明">
|
|
|
|
|
- <el-input type="textarea" v-model="dispatchForm.remark" placeholder="填写任务要求、注意事项等" rows="3" />
|
|
|
|
|
|
|
+ <el-form-item label="问题描述" required>
|
|
|
|
|
+ <el-input type="textarea" v-model="dispatchForm.orderDesc" placeholder="填写问题描述、维修要求等" :rows="3" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="期望完成时间">
|
|
|
|
|
- <el-date-picker v-model="dispatchForm.deadline" type="datetime" placeholder="选择截止时间" style="width: 100%" />
|
|
|
|
|
|
|
+ <el-form-item label="负责人">
|
|
|
|
|
+ <el-input v-model="dispatchForm.owner" placeholder="填写负责人姓名(选填)" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<el-button @click="dispatchDialogVisible = false">取消</el-button>
|
|
<el-button @click="dispatchDialogVisible = false">取消</el-button>
|
|
|
- <el-button type="primary" @click="confirmDispatch">确认派发</el-button>
|
|
|
|
|
|
|
+ <el-button type="primary" @click="confirmDispatch" :loading="submitting">确认提交</el-button>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <!-- 批量派发确认对话框 -->
|
|
|
|
|
- <el-dialog v-model="batchDispatchDialogVisible" title="批量派发确认" width="450px">
|
|
|
|
|
- <p>将向以下 {{ batchDispatchIds.length }} 个异常终端派发任务:</p>
|
|
|
|
|
|
|
+ <!-- 批量提交工单对话框 -->
|
|
|
|
|
+ <el-dialog v-model="batchDispatchDialogVisible" title="批量提交工单" width="450px">
|
|
|
|
|
+ <p>将向以下 {{ batchDispatchIds.length }} 个异常设备提交工单:</p>
|
|
|
<div class="batch-list">
|
|
<div class="batch-list">
|
|
|
<el-tag v-for="id in batchDispatchIds.slice(0, 5)" :key="id" size="small" style="margin: 4px">
|
|
<el-tag v-for="id in batchDispatchIds.slice(0, 5)" :key="id" size="small" style="margin: 4px">
|
|
|
{{ getTerminalCode(id) }}
|
|
{{ getTerminalCode(id) }}
|
|
|
</el-tag>
|
|
</el-tag>
|
|
|
- <span v-if="batchDispatchIds.length > 5">等{{ batchDispatchIds.length }}个终端</span>
|
|
|
|
|
|
|
+ <span v-if="batchDispatchIds.length > 5">等{{ batchDispatchIds.length }}个设备</span>
|
|
|
</div>
|
|
</div>
|
|
|
- <el-form style="margin-top: 16px">
|
|
|
|
|
- <el-form-item label="派发对象">
|
|
|
|
|
- <el-select v-model="batchDispatchForm.targetType" placeholder="选择派发类型" style="width: 100%">
|
|
|
|
|
- <el-option label="施工单位" value="construction" />
|
|
|
|
|
- <el-option label="维护人员" value="maintainer" />
|
|
|
|
|
|
|
+ <el-form style="margin-top: 16px" label-width="80px">
|
|
|
|
|
+ <el-form-item label="工单类型">
|
|
|
|
|
+ <el-select v-model="batchDispatchForm.orderType" style="width: 100%">
|
|
|
|
|
+ <el-option label="故障维修" :value="1" />
|
|
|
|
|
+ <el-option label="日常巡检" :value="2" />
|
|
|
|
|
+ <el-option label="设备保养" :value="3" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="选择单位/人员">
|
|
|
|
|
- <el-select v-model="batchDispatchForm.targetId" placeholder="请选择" style="width: 100%">
|
|
|
|
|
- <el-option v-for="item in batchDispatchTargets" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
|
|
|
|
+ <el-form-item label="优先级">
|
|
|
|
|
+ <el-select v-model="batchDispatchForm.orderLevel" style="width: 100%">
|
|
|
|
|
+ <el-option label="紧急" :value="1" />
|
|
|
|
|
+ <el-option label="一般" :value="2" />
|
|
|
|
|
+ <el-option label="低" :value="3" />
|
|
|
</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>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<el-button @click="batchDispatchDialogVisible = false">取消</el-button>
|
|
<el-button @click="batchDispatchDialogVisible = false">取消</el-button>
|
|
|
- <el-button type="primary" @click="confirmBatchDispatch">确认批量派发</el-button>
|
|
|
|
|
|
|
+ <el-button type="primary" @click="confirmBatchDispatch" :loading="submitting">确认批量提交</el-button>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import { ref, computed } from 'vue'
|
|
|
|
|
-import { Warning, Search, Refresh, Location, Monitor, MapLocation, Document } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
+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 { ElMessage } from 'element-plus'
|
|
|
-
|
|
|
|
|
-// 模拟异常终端数据
|
|
|
|
|
-const mockTerminals = ref([
|
|
|
|
|
- {
|
|
|
|
|
- id: 1, code: 'TM-1001', name: '人民路智能井盖', deviceType: 'smart', deviceTypeName: '智能井盖',
|
|
|
|
|
- model: 'WATCHMAN-S2', ownerUnit: '市政工程管理处', abnormalLevel: 'critical',
|
|
|
|
|
- abnormalDesc: '倾斜角度14.2°,超过阈值8°,有跌落风险', abnormalTime: '2025-04-02 14:23:15',
|
|
|
|
|
- status: 'pending', lng: 121.487, lat: 31.249, address: '人民路与解放路口东50m', locationShort: '人民路口',
|
|
|
|
|
- historyRecords: []
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 2, code: 'TM-1002', name: '滨江路防爆井盖', deviceType: 'explosion', deviceTypeName: '防爆井盖',
|
|
|
|
|
- model: 'WATCHMAN-EX', ownerUnit: '水务集团', abnormalLevel: 'critical',
|
|
|
|
|
- abnormalDesc: '水位62cm,超过阈值40cm,溢水风险', abnormalTime: '2025-04-02 13:55:02',
|
|
|
|
|
- status: 'pending', lng: 121.502, lat: 31.235, address: '滨江路化工园区南门', locationShort: '滨江园区',
|
|
|
|
|
- historyRecords: []
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 3, code: 'TM-1003', name: '古城街井盖', deviceType: 'smart', deviceTypeName: '智能井盖',
|
|
|
|
|
- model: 'WATCHMAN-S1', ownerUnit: '市政设施管理处', abnormalLevel: 'warning',
|
|
|
|
|
- abnormalDesc: '异常震动112mg,超过阈值80mg', abnormalTime: '2025-04-02 12:10:33',
|
|
|
|
|
- status: 'pending', lng: 121.478, lat: 31.258, address: '古城街北段树下', locationShort: '古城街',
|
|
|
|
|
- historyRecords: [
|
|
|
|
|
- { id: 1, time: '2025-03-28 10:30:00', type: 'primary', dispatchedTo: '维修一组-张建国', description: '现场检查为车辆碾压导致,已加固' }
|
|
|
|
|
- ]
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 4, code: 'TM-1004', name: '南港路井盖', deviceType: 'smart', deviceTypeName: '智能井盖',
|
|
|
|
|
- model: 'WATCHMAN-S2', ownerUnit: '市政工程管理处', abnormalLevel: 'warning',
|
|
|
|
|
- abnormalDesc: '井盖异常开启', abnormalTime: '2025-04-02 11:45:20',
|
|
|
|
|
- status: 'pending', lng: 121.495, lat: 31.228, address: '南港路旧车站段', locationShort: '南港路',
|
|
|
|
|
- historyRecords: []
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 5, code: 'TM-1005', name: '石化区防爆井盖', deviceType: 'explosion', deviceTypeName: '防爆井盖',
|
|
|
|
|
- model: 'WATCHMAN-EX', ownerUnit: '生态环境局', abnormalLevel: 'critical',
|
|
|
|
|
- abnormalDesc: 'H₂S浓度28ppm,超过阈值15ppm', abnormalTime: '2025-04-02 10:30:45',
|
|
|
|
|
- status: 'pending', lng: 121.524, lat: 31.239, address: '石化大道南侧', locationShort: '石化大道',
|
|
|
|
|
- historyRecords: [
|
|
|
|
|
- { id: 1, time: '2025-03-30 09:00:00', type: 'success', dispatchedTo: '应急抢修队-赵志远', description: '已通风处理,浓度降至安全范围' }
|
|
|
|
|
- ]
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 6, code: 'TM-1006', name: '高新区智能井盖', deviceType: 'smart', deviceTypeName: '智能井盖',
|
|
|
|
|
- model: 'WATCHMAN-S2', ownerUnit: '市政设施管理处', abnormalLevel: 'warning',
|
|
|
|
|
- abnormalDesc: '电池电量仅剩12%,需及时更换', abnormalTime: '2025-04-02 08:45:00',
|
|
|
|
|
- status: 'pending', lng: 121.512, lat: 31.242, address: '高新大道云计算中心旁', locationShort: '高新区',
|
|
|
|
|
- historyRecords: []
|
|
|
|
|
- }
|
|
|
|
|
-])
|
|
|
|
|
-
|
|
|
|
|
-// 派发目标选项
|
|
|
|
|
-const constructionUnits = [
|
|
|
|
|
- { id: 'c1', name: '市政一建施工队', type: 'construction' },
|
|
|
|
|
- { id: 'c2', name: '水务工程公司', type: 'construction' },
|
|
|
|
|
- { id: 'c3', name: '管道抢修中心', type: 'construction' }
|
|
|
|
|
-]
|
|
|
|
|
-const maintainers = [
|
|
|
|
|
- { id: 'm1', name: '张建国 - 维修一组', type: 'maintainer' },
|
|
|
|
|
- { id: 'm2', name: '李志强 - 维修二组', type: 'maintainer' },
|
|
|
|
|
- { id: 'm3', name: '王大明 - 应急抢修队', type: 'maintainer' },
|
|
|
|
|
- { id: 'm4', name: '陈辉 - 巡检专员', type: 'maintainer' }
|
|
|
|
|
-]
|
|
|
|
|
-
|
|
|
|
|
-// 筛选条件
|
|
|
|
|
|
|
+import { getManholeAlarmMonitorList, addManholeWorkOrder, getManholeWorkOrderProcessingPage } from '@/api/pipeNetwork/basic'
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 常量 ====================
|
|
|
|
|
+const TYPE_MAP = { '5': '供水窨井', '9': '排水窨井', '10': '燃气窨井' }
|
|
|
|
|
+const LOW_BATTERY_THRESHOLD = 15
|
|
|
|
|
+const LOW_SIGNAL_THRESHOLD = 20
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 状态 ====================
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const loadingOrders = ref(false)
|
|
|
|
|
+const submitting = ref(false)
|
|
|
|
|
+const devices = ref([])
|
|
|
const searchKeyword = ref('')
|
|
const searchKeyword = ref('')
|
|
|
const deviceTypeFilter = ref('')
|
|
const deviceTypeFilter = ref('')
|
|
|
const currentPage = ref(1)
|
|
const currentPage = ref(1)
|
|
|
-const pageSize = 6
|
|
|
|
|
|
|
+const pageSize = 8
|
|
|
|
|
|
|
|
-// 选择相关
|
|
|
|
|
const selectedRows = ref([])
|
|
const selectedRows = ref([])
|
|
|
const selectAll = ref(false)
|
|
const selectAll = ref(false)
|
|
|
-const selectedTerminal = ref(mockTerminals.value[0])
|
|
|
|
|
|
|
+const selectedTerminal = ref(null)
|
|
|
const activeTab = ref('info')
|
|
const activeTab = ref('info')
|
|
|
|
|
|
|
|
-// 派发相关
|
|
|
|
|
|
|
+const workOrders = ref([])
|
|
|
|
|
+
|
|
|
const dispatchDialogVisible = ref(false)
|
|
const dispatchDialogVisible = ref(false)
|
|
|
const currentDispatchTerminal = ref(null)
|
|
const currentDispatchTerminal = ref(null)
|
|
|
-const dispatchForm = ref({
|
|
|
|
|
- targetType: 'construction',
|
|
|
|
|
- targetId: '',
|
|
|
|
|
- remark: '',
|
|
|
|
|
- deadline: ''
|
|
|
|
|
-})
|
|
|
|
|
|
|
+const dispatchForm = ref({ orderType: 1, orderLevel: 2, orderDesc: '', owner: '' })
|
|
|
|
|
+
|
|
|
const batchDispatchDialogVisible = ref(false)
|
|
const batchDispatchDialogVisible = ref(false)
|
|
|
const batchDispatchIds = ref([])
|
|
const batchDispatchIds = ref([])
|
|
|
-const batchDispatchForm = ref({
|
|
|
|
|
- targetType: 'construction',
|
|
|
|
|
- targetId: ''
|
|
|
|
|
-})
|
|
|
|
|
|
|
+const batchDispatchForm = ref({ orderType: 1, orderLevel: 2, orderDesc: '' })
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 数据加载 ====================
|
|
|
|
|
+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
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-// 计算属性
|
|
|
|
|
-const abnormalTerminals = computed(() => {
|
|
|
|
|
- return mockTerminals.value.filter(t => t.status === 'pending')
|
|
|
|
|
-})
|
|
|
|
|
|
|
+async function loadWorkOrders(deviceId) {
|
|
|
|
|
+ if (!deviceId) return
|
|
|
|
|
+ loadingOrders.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getManholeWorkOrderProcessingPage({
|
|
|
|
|
+ deviceId,
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 20
|
|
|
|
|
+ })
|
|
|
|
|
+ workOrders.value = res.data?.records || res.rows || []
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ workOrders.value = []
|
|
|
|
|
+ console.error('[operationTask] 工单记录加载失败:', error)
|
|
|
|
|
+ } 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}%`)
|
|
|
|
|
+
|
|
|
|
|
+ // 推荐工单类型
|
|
|
|
|
+ let suggestedOrderType = 1
|
|
|
|
|
+ if (lowBattery) suggestedOrderType = 3
|
|
|
|
|
+ else if (lowSignal) suggestedOrderType = 2
|
|
|
|
|
+
|
|
|
|
|
+ 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,
|
|
|
|
|
+ suggestedOrderType
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 计算属性 ====================
|
|
|
const filteredTerminals = computed(() => {
|
|
const filteredTerminals = computed(() => {
|
|
|
- let list = abnormalTerminals.value
|
|
|
|
|
|
|
+ let list = devices.value
|
|
|
if (searchKeyword.value) {
|
|
if (searchKeyword.value) {
|
|
|
const kw = searchKeyword.value.toLowerCase()
|
|
const kw = searchKeyword.value.toLowerCase()
|
|
|
list = list.filter(t =>
|
|
list = list.filter(t =>
|
|
|
- t.code.toLowerCase().includes(kw) ||
|
|
|
|
|
- t.name.toLowerCase().includes(kw) ||
|
|
|
|
|
- t.address.toLowerCase().includes(kw)
|
|
|
|
|
|
|
+ t.code.toLowerCase().includes(kw) ||
|
|
|
|
|
+ t.name.toLowerCase().includes(kw) ||
|
|
|
|
|
+ t.location.toLowerCase().includes(kw)
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
if (deviceTypeFilter.value) {
|
|
if (deviceTypeFilter.value) {
|
|
|
- list = list.filter(t => t.deviceType === deviceTypeFilter.value)
|
|
|
|
|
|
|
+ list = list.filter(t => t.typeId === deviceTypeFilter.value)
|
|
|
}
|
|
}
|
|
|
return list
|
|
return list
|
|
|
})
|
|
})
|
|
@@ -370,37 +472,29 @@ const paginatedTerminals = computed(() => {
|
|
|
return filteredTerminals.value.slice(start, start + pageSize)
|
|
return filteredTerminals.value.slice(start, start + pageSize)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-const abnormalCount = computed(() => abnormalTerminals.value.length)
|
|
|
|
|
-const criticalCount = computed(() => abnormalTerminals.value.filter(t => t.abnormalLevel === 'critical').length)
|
|
|
|
|
-const warningCount = computed(() => abnormalTerminals.value.filter(t => t.abnormalLevel === 'warning').length)
|
|
|
|
|
-const pendingCount = computed(() => abnormalTerminals.value.filter(t => t.status === 'pending').length)
|
|
|
|
|
-const todayDispatched = computed(() => {
|
|
|
|
|
- // 模拟今日派发数量
|
|
|
|
|
- return 3
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 派发目标动态计算
|
|
|
|
|
-const dispatchTargets = computed(() => {
|
|
|
|
|
- if (dispatchForm.value.targetType === 'construction') {
|
|
|
|
|
- return constructionUnits
|
|
|
|
|
- } else {
|
|
|
|
|
- return maintainers
|
|
|
|
|
- }
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-const batchDispatchTargets = computed(() => {
|
|
|
|
|
- if (batchDispatchForm.value.targetType === 'construction') {
|
|
|
|
|
- return constructionUnits
|
|
|
|
|
- } else {
|
|
|
|
|
- return maintainers
|
|
|
|
|
- }
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 方法
|
|
|
|
|
-const refreshList = () => {
|
|
|
|
|
- ElMessage.success('列表已刷新')
|
|
|
|
|
|
|
+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 orderTypeText = (t) => ({ 1: '故障维修', 2: '日常巡检', 3: '设备保养' }[t] || '—')
|
|
|
|
|
+const priorityText = (l) => ({ 1: '紧急', 2: '一般', 3: '低' }[l] || '—')
|
|
|
|
|
+const orderStatusText = (s) => ({
|
|
|
|
|
+ 1: '待派单', 2: '待接单', 3: '已接单', 4: '处理中',
|
|
|
|
|
+ 5: '待验收', 6: '已完成', 7: '已关闭', 8: '延期审核', 9: '已归档'
|
|
|
|
|
+}[s] || '—')
|
|
|
|
|
+const orderLevelTagType = (l) => ({ 1: 'danger', 2: 'warning', 3: '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) => {
|
|
const handleSelectAll = (val) => {
|
|
|
if (val) {
|
|
if (val) {
|
|
|
selectedRows.value = paginatedTerminals.value.map(t => t.id)
|
|
selectedRows.value = paginatedTerminals.value.map(t => t.id)
|
|
@@ -411,13 +505,12 @@ const handleSelectAll = (val) => {
|
|
|
|
|
|
|
|
const toggleSelect = (id, val) => {
|
|
const toggleSelect = (id, val) => {
|
|
|
if (val) {
|
|
if (val) {
|
|
|
- selectedRows.value.push(id)
|
|
|
|
|
|
|
+ if (!selectedRows.value.includes(id)) selectedRows.value.push(id)
|
|
|
} else {
|
|
} else {
|
|
|
selectedRows.value = selectedRows.value.filter(i => i !== id)
|
|
selectedRows.value = selectedRows.value.filter(i => i !== id)
|
|
|
}
|
|
}
|
|
|
- // 更新全选状态
|
|
|
|
|
selectAll.value = paginatedTerminals.value.length > 0 &&
|
|
selectAll.value = paginatedTerminals.value.length > 0 &&
|
|
|
- paginatedTerminals.value.every(t => selectedRows.value.includes(t.id))
|
|
|
|
|
|
|
+ paginatedTerminals.value.every(t => selectedRows.value.includes(t.id))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const clearSelection = () => {
|
|
const clearSelection = () => {
|
|
@@ -427,99 +520,126 @@ const clearSelection = () => {
|
|
|
|
|
|
|
|
const selectTerminal = (terminal) => {
|
|
const selectTerminal = (terminal) => {
|
|
|
selectedTerminal.value = terminal
|
|
selectedTerminal.value = terminal
|
|
|
|
|
+ if (activeTab.value === 'history') {
|
|
|
|
|
+ loadWorkOrders(terminal.equipmentId)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const onTabChange = (tabName) => {
|
|
|
|
|
+ if (tabName === 'history' && selectedTerminal.value) {
|
|
|
|
|
+ loadWorkOrders(selectedTerminal.value.equipmentId)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const getTerminalCode = (id) => {
|
|
const getTerminalCode = (id) => {
|
|
|
- const terminal = mockTerminals.value.find(t => t.id === id)
|
|
|
|
|
|
|
+ const terminal = devices.value.find(t => t.id === id)
|
|
|
return terminal?.code || id
|
|
return terminal?.code || id
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 单个派发
|
|
|
|
|
|
|
+// ==================== 提交工单 ====================
|
|
|
const openDispatchDialog = (terminal) => {
|
|
const openDispatchDialog = (terminal) => {
|
|
|
currentDispatchTerminal.value = terminal
|
|
currentDispatchTerminal.value = terminal
|
|
|
dispatchForm.value = {
|
|
dispatchForm.value = {
|
|
|
- targetType: 'construction',
|
|
|
|
|
- targetId: '',
|
|
|
|
|
- remark: '',
|
|
|
|
|
- deadline: ''
|
|
|
|
|
|
|
+ orderType: terminal.suggestedOrderType || 1,
|
|
|
|
|
+ orderLevel: terminal.abnormalLevel === 'critical' ? 1 : 2,
|
|
|
|
|
+ orderDesc: terminal.abnormalDesc || '',
|
|
|
|
|
+ owner: ''
|
|
|
}
|
|
}
|
|
|
dispatchDialogVisible.value = true
|
|
dispatchDialogVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const confirmDispatch = () => {
|
|
|
|
|
- if (!dispatchForm.value.targetId) {
|
|
|
|
|
- ElMessage.warning('请选择派发对象')
|
|
|
|
|
|
|
+const confirmDispatch = async () => {
|
|
|
|
|
+ if (!dispatchForm.value.orderDesc?.trim()) {
|
|
|
|
|
+ ElMessage.warning('请填写问题描述')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- const target = dispatchTargets.value.find(t => t.id === dispatchForm.value.targetId)
|
|
|
|
|
- if (currentDispatchTerminal.value) {
|
|
|
|
|
- // 更新终端状态
|
|
|
|
|
- currentDispatchTerminal.value.status = 'processing'
|
|
|
|
|
- // 添加处置记录
|
|
|
|
|
- const newRecord = {
|
|
|
|
|
- id: Date.now(),
|
|
|
|
|
- time: new Date().toLocaleString(),
|
|
|
|
|
- type: 'primary',
|
|
|
|
|
- dispatchedTo: target.name,
|
|
|
|
|
- description: dispatchForm.value.remark || '派发任务,请及时处理',
|
|
|
|
|
- deadline: dispatchForm.value.deadline ? new Date(dispatchForm.value.deadline).toLocaleString() : null
|
|
|
|
|
- }
|
|
|
|
|
- if (!currentDispatchTerminal.value.historyRecords) {
|
|
|
|
|
- currentDispatchTerminal.value.historyRecords = []
|
|
|
|
|
|
|
+ if (!currentDispatchTerminal.value?.equipmentId) {
|
|
|
|
|
+ ElMessage.warning('设备信息缺失,无法提交工单')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ submitting.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const terminal = currentDispatchTerminal.value
|
|
|
|
|
+ const desc = dispatchForm.value.owner
|
|
|
|
|
+ ? `${dispatchForm.value.orderDesc}(负责人:${dispatchForm.value.owner})`
|
|
|
|
|
+ : dispatchForm.value.orderDesc
|
|
|
|
|
+ await addManholeWorkOrder({
|
|
|
|
|
+ deviceId: terminal.equipmentId,
|
|
|
|
|
+ orderType: dispatchForm.value.orderType,
|
|
|
|
|
+ orderLevel: dispatchForm.value.orderLevel,
|
|
|
|
|
+ orderDesc: desc
|
|
|
|
|
+ })
|
|
|
|
|
+ // 标记已派发
|
|
|
|
|
+ terminal.workOrderSubmitted = true
|
|
|
|
|
+ ElMessage.success('工单已提交,相关部门将即刻处理')
|
|
|
|
|
+ dispatchDialogVisible.value = false
|
|
|
|
|
+ // 如果在处置记录tab,刷新工单列表
|
|
|
|
|
+ if (activeTab.value === 'history') {
|
|
|
|
|
+ loadWorkOrders(terminal.equipmentId)
|
|
|
}
|
|
}
|
|
|
- currentDispatchTerminal.value.historyRecords.unshift(newRecord)
|
|
|
|
|
-
|
|
|
|
|
- ElMessage.success(`已派发任务至 ${target.name}`)
|
|
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ const msg = error?.message || error?.msg || '未知错误'
|
|
|
|
|
+ ElMessage.error('工单提交失败: ' + msg)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ submitting.value = false
|
|
|
}
|
|
}
|
|
|
- dispatchDialogVisible.value = false
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 批量派发
|
|
|
|
|
|
|
+// ==================== 批量提交工单 ====================
|
|
|
const batchDispatch = () => {
|
|
const batchDispatch = () => {
|
|
|
if (selectedRows.value.length === 0) {
|
|
if (selectedRows.value.length === 0) {
|
|
|
- ElMessage.warning('请先选择异常终端')
|
|
|
|
|
|
|
+ ElMessage.warning('请先选择异常设备')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
batchDispatchIds.value = [...selectedRows.value]
|
|
batchDispatchIds.value = [...selectedRows.value]
|
|
|
- batchDispatchForm.value = {
|
|
|
|
|
- targetType: 'construction',
|
|
|
|
|
- targetId: ''
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ batchDispatchForm.value = { orderType: 1, orderLevel: 2, orderDesc: '' }
|
|
|
batchDispatchDialogVisible.value = true
|
|
batchDispatchDialogVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const confirmBatchDispatch = () => {
|
|
|
|
|
- if (!batchDispatchForm.value.targetId) {
|
|
|
|
|
- ElMessage.warning('请选择派发对象')
|
|
|
|
|
|
|
+const confirmBatchDispatch = async () => {
|
|
|
|
|
+ if (!batchDispatchForm.value.orderDesc?.trim()) {
|
|
|
|
|
+ ElMessage.warning('请填写问题描述')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- const target = batchDispatchTargets.value.find(t => t.id === batchDispatchForm.value.targetId)
|
|
|
|
|
-
|
|
|
|
|
- // 批量更新所有选中的终端
|
|
|
|
|
- batchDispatchIds.value.forEach(id => {
|
|
|
|
|
- const terminal = mockTerminals.value.find(t => t.id === id)
|
|
|
|
|
- if (terminal && terminal.status === 'pending') {
|
|
|
|
|
- terminal.status = 'processing'
|
|
|
|
|
- const newRecord = {
|
|
|
|
|
- id: Date.now() + Math.random(),
|
|
|
|
|
- time: new Date().toLocaleString(),
|
|
|
|
|
- type: 'primary',
|
|
|
|
|
- dispatchedTo: target.name,
|
|
|
|
|
- description: `批量派发任务,${batchDispatchForm.value.targetType === 'construction' ? '请施工单位' : '请维护人员'}及时处理`
|
|
|
|
|
|
|
+ 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,
|
|
|
|
|
+ orderType: batchDispatchForm.value.orderType,
|
|
|
|
|
+ orderLevel: batchDispatchForm.value.orderLevel,
|
|
|
|
|
+ orderDesc: batchDispatchForm.value.orderDesc
|
|
|
|
|
+ })
|
|
|
|
|
+ terminal.workOrderSubmitted = true
|
|
|
|
|
+ successCount++
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ failCount++
|
|
|
}
|
|
}
|
|
|
- if (!terminal.historyRecords) terminal.historyRecords = []
|
|
|
|
|
- terminal.historyRecords.unshift(newRecord)
|
|
|
|
|
}
|
|
}
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- ElMessage.success(`已批量派发 ${batchDispatchIds.value.length} 个任务至 ${target.name}`)
|
|
|
|
|
- batchDispatchDialogVisible.value = false
|
|
|
|
|
- clearSelection()
|
|
|
|
|
|
|
+ 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
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const onTargetTypeChange = () => {
|
|
|
|
|
- dispatchForm.value.targetId = ''
|
|
|
|
|
-}
|
|
|
|
|
|
|
+// ==================== 生命周期 ====================
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ loadDevices()
|
|
|
|
|
+})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
@@ -539,7 +659,7 @@ const onTargetTypeChange = () => {
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
flex-wrap: wrap;
|
|
flex-wrap: wrap;
|
|
|
gap: 12px;
|
|
gap: 12px;
|
|
|
- box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
|
|
|
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.header-left {
|
|
.header-left {
|
|
@@ -574,7 +694,7 @@ const onTargetTypeChange = () => {
|
|
|
padding: 12px 24px;
|
|
padding: 12px 24px;
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
min-width: 110px;
|
|
min-width: 110px;
|
|
|
- box-shadow: 0 1px 4px rgba(0,0,0,0.05);
|
|
|
|
|
|
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.stat-card.critical .stat-value { color: #f56c6c; }
|
|
.stat-card.critical .stat-value { color: #f56c6c; }
|
|
@@ -648,20 +768,34 @@ const onTargetTypeChange = () => {
|
|
|
.terminal-header {
|
|
.terminal-header {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
- margin-bottom: 6px;
|
|
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.terminal-code { font-weight: 600; font-size: 14px; }
|
|
.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-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-desc { font-size: 12px; color: #f56c6c; margin-bottom: 4px; }
|
|
|
.terminal-time { font-size: 11px; color: #aaa; }
|
|
.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 {
|
|
.detail-panel {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
background: white;
|
|
background: white;
|
|
|
border-radius: 16px;
|
|
border-radius: 16px;
|
|
|
padding: 20px;
|
|
padding: 20px;
|
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
|
|
|
+ max-height: calc(100vh - 160px);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.empty-panel {
|
|
.empty-panel {
|
|
@@ -682,22 +816,57 @@ const onTargetTypeChange = () => {
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.map-container { margin-bottom: 20px; }
|
|
|
|
|
-.mock-map {
|
|
|
|
|
- background: #e9f5e9;
|
|
|
|
|
- border-radius: 12px;
|
|
|
|
|
- height: 140px;
|
|
|
|
|
|
|
+.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;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- justify-content: center;
|
|
|
|
|
- gap: 16px;
|
|
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #303133;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.location-info .coords { color: #909399; font-size: 12px; }
|
|
|
|
|
+
|
|
|
.record-content { font-size: 13px; }
|
|
.record-content { font-size: 13px; }
|
|
|
-.record-images { display: flex; gap: 8px; margin-top: 8px; }
|
|
|
|
|
-.history-img { width: 60px; height: 50px; border-radius: 6px; object-fit: cover; }
|
|
|
|
|
|
|
+.record-row { margin-bottom: 4px; }
|
|
|
|
|
|
|
|
.pagination-area { padding: 12px; text-align: center; border-top: 1px solid #eee; }
|
|
.pagination-area { padding: 12px; text-align: center; border-top: 1px solid #eee; }
|
|
|
|
|
|
|
|
.batch-list { max-height: 150px; overflow-y: auto; }
|
|
.batch-list { max-height: 150px; overflow-y: auto; }
|
|
|
-</style>
|
|
|
|
|
|
|
+</style>
|