|
@@ -1,7 +1,15 @@
|
|
|
<!-- 物联感知设备管理 -->
|
|
<!-- 物联感知设备管理 -->
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="app-container">
|
|
|
|
|
- <!-- 设备统计 -->
|
|
|
|
|
|
|
+ <div class="app-container equipment-page">
|
|
|
|
|
+ <!-- 页头 -->
|
|
|
|
|
+ <div class="page-head">
|
|
|
|
|
+ <div class="page-head-titles">
|
|
|
|
|
+ <div class="page-title">设备统计</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-button type="primary" icon="Plus" class="btn-primary" @click="handleAdd">新增设备</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- KPI -->
|
|
|
<div class="kpi-row">
|
|
<div class="kpi-row">
|
|
|
<el-card shadow="never" class="kpi">
|
|
<el-card shadow="never" class="kpi">
|
|
|
<div class="kpi-icon total"><el-icon><monitor /></el-icon></div>
|
|
<div class="kpi-icon total"><el-icon><monitor /></el-icon></div>
|
|
@@ -33,85 +41,123 @@
|
|
|
</el-card>
|
|
</el-card>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 查询条件 -->
|
|
|
|
|
- <el-form ref="queryRef" :model="queryParams" :inline="true" v-show="showSearch" label-width="80px">
|
|
|
|
|
- <el-form-item label="设备编码" prop="equipmentCode">
|
|
|
|
|
- <el-input v-model="queryParams.equipmentCode" placeholder="请输入设备编码" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="设备名称" prop="equipmentName">
|
|
|
|
|
- <el-input v-model="queryParams.equipmentName" placeholder="请输入设备名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="设备类型" prop="equipmentTypeId">
|
|
|
|
|
- <el-select v-model="queryParams.equipmentTypeId" placeholder="请选择设备类型" clearable style="width: 200px">
|
|
|
|
|
- <el-option v-for="item in equipmentSubtypes" :key="item.id" :label="item.typeName" :value="item.id" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="运行状态">
|
|
|
|
|
- <el-select v-model="runStatus" placeholder="全部" clearable style="width: 160px">
|
|
|
|
|
- <el-option label="在线" value="online" />
|
|
|
|
|
- <el-option label="离线" value="offline" />
|
|
|
|
|
- <el-option label="报警" value="alarm" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </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>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 工具栏 -->
|
|
|
|
|
- <el-row :gutter="10" class="mb8">
|
|
|
|
|
- <el-col :span="1.5">
|
|
|
|
|
- <el-button type="primary" plain icon="Plus" @click="handleAdd">新增设备</el-button>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
|
|
|
|
|
- </el-row>
|
|
|
|
|
-
|
|
|
|
|
<!-- 设备台账 -->
|
|
<!-- 设备台账 -->
|
|
|
- <el-table v-loading="loading" :data="equipmentList" @row-dblclick="handleView">
|
|
|
|
|
- <el-table-column prop="equipmentCode" label="设备编码" min-width="140" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column prop="equipmentName" label="设备名称" min-width="160" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column label="设备类型" min-width="140" show-overflow-tooltip>
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- {{ (row.equipmentTypeId && typeNameMap[row.equipmentTypeId]) || "未分类" }}
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column label="所属片区" width="100" align="center">
|
|
|
|
|
- <template #default="{ row }">{{ districtLabel(row.district) }}</template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="equipmentModel" label="设备型号" min-width="130" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column prop="equipmentLocation" label="安装位置" min-width="200" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column label="运行状态" width="140" align="center">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- <el-tag :type="row.onlineStatus === 1 ? 'success' : 'info'" size="small">
|
|
|
|
|
- {{ row.onlineStatus === 1 ? "在线" : "离线" }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
- <el-tag v-if="row.alarmStatus === 1" type="danger" size="small" style="margin-left: 4px">报警</el-tag>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column label="资产状态" width="100" align="center">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- <el-tag :type="currentStatusMeta(row.currentStatus).type" size="small">
|
|
|
|
|
- {{ currentStatusMeta(row.currentStatus).text }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column label="操作" width="200" align="center" fixed="right" class-name="small-padding fixed-width">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- <el-button link type="primary" icon="View" @click="handleView(row)">详情</el-button>
|
|
|
|
|
- <el-button link type="primary" icon="Edit" @click="handleUpdate(row)">修改</el-button>
|
|
|
|
|
- <el-button link type="danger" icon="Delete" @click="handleDelete(row)">删除</el-button>
|
|
|
|
|
|
|
+ <div class="page-title section-block-title">设备台账</div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 查询条件 -->
|
|
|
|
|
+ <el-card shadow="never" class="filter-card">
|
|
|
|
|
+ <div class="type-filter">
|
|
|
|
|
+ <div class="type-filter-title">设备类型</div>
|
|
|
|
|
+ <div class="type-filter-label">一级类型</div>
|
|
|
|
|
+ <div class="type-filter-options" role="group" aria-label="一级类型">
|
|
|
|
|
+ <button type="button" class="type-category" :aria-pressed="selectedCategoryId === undefined" @click="selectCategory(undefined)">全部</button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-for="item in equipmentTypeTree"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="type-category"
|
|
|
|
|
+ :aria-pressed="selectedCategoryId === item.id"
|
|
|
|
|
+ @click="selectCategory(item.id)"
|
|
|
|
|
+ >{{ item.typeName }}</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <template v-if="selectedCategory">
|
|
|
|
|
+ <div class="type-filter-label">二级类型</div>
|
|
|
|
|
+ <div class="type-filter-options" role="group" aria-label="二级类型">
|
|
|
|
|
+ <button type="button" class="type-chip" :aria-pressed="queryParams.equipmentTypeId === undefined" @click="selectEquipmentType(undefined)">全部</button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-for="item in equipmentSubtypes"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="type-chip"
|
|
|
|
|
+ :aria-pressed="queryParams.equipmentTypeId === item.id"
|
|
|
|
|
+ @click="selectEquipmentType(item.id)"
|
|
|
|
|
+ >{{ item.typeName }}</button>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
- </el-table-column>
|
|
|
|
|
- </el-table>
|
|
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-form ref="queryRef" :model="queryParams" :inline="true" class="filters">
|
|
|
|
|
+ <el-form-item label="设备编码" prop="equipmentCode">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="queryParams.equipmentCode"
|
|
|
|
|
+ placeholder="请输入"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ class="input--code"
|
|
|
|
|
+ @keyup.enter="handleQuery"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="设备名称" prop="equipmentName">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="queryParams.equipmentName"
|
|
|
|
|
+ placeholder="请输入"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ class="input--name"
|
|
|
|
|
+ @keyup.enter="handleQuery"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="在线状态" prop="onlineStatus">
|
|
|
|
|
+ <el-select v-model="queryParams.onlineStatus" clearable placeholder="请选择" class="select--online">
|
|
|
|
|
+ <el-option label="在线" :value="1" />
|
|
|
|
|
+ <el-option label="离线" :value="0" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="报警" prop="alarmOnly">
|
|
|
|
|
+ <el-checkbox v-model="alarmOnly" @change="handleQuery" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" class="btn-query" @click="handleQuery">查询</el-button>
|
|
|
|
|
+ <el-button class="btn-reset" @click="resetQuery">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-card shadow="never" class="ledger">
|
|
|
|
|
+ <div ref="gridRef" v-loading="loading" class="grid">
|
|
|
|
|
+ <el-empty v-if="!equipmentList.length" description="暂无设备数据" />
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="row in equipmentList"
|
|
|
|
|
+ :key="row.equipmentId"
|
|
|
|
|
+ class="card"
|
|
|
|
|
+ :class="{ 'is-selected': selectedId === row.equipmentId, 'is-alarm': getRowAlarm(row.equipmentId) === 1 }"
|
|
|
|
|
+ @click="handleView(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="card-top">
|
|
|
|
|
+ <div class="card-name">{{ row.equipmentName || "未命名设备" }}</div>
|
|
|
|
|
+ <div class="status-group">
|
|
|
|
|
+ <span class="badge" :class="getRowOnline(row.equipmentId) === 1 ? 'online' : 'offline'">
|
|
|
|
|
+ <span class="badge-dot"></span>
|
|
|
|
|
+ <span class="badge-text">{{ getRowOnline(row.equipmentId) === 1 ? "在线" : "离线" }}</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span v-if="getRowAlarm(row.equipmentId) === 1" class="badge alarm">
|
|
|
|
|
+ <el-icon class="badge-icon"><warning-filled /></el-icon>
|
|
|
|
|
+ <span class="badge-text">报警</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <pagination
|
|
|
|
|
- v-show="total > 0"
|
|
|
|
|
- :total="total"
|
|
|
|
|
- v-model:page="queryParams.pageNum"
|
|
|
|
|
- v-model:limit="queryParams.pageSize"
|
|
|
|
|
- @pagination="getList"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <div class="card-code">{{ row.equipmentCode }}</div>
|
|
|
|
|
+ <div class="card-district">片区:{{ districtLabel(row.district) }}</div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="card-bottom">
|
|
|
|
|
+ <span class="type-tag">
|
|
|
|
|
+ {{ (row.equipmentTypeId && typeNameMap[row.equipmentTypeId]) || "未分类" }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <div class="card-actions">
|
|
|
|
|
+ <el-button link class="link-detail" @click.stop="handleView(row)">详情</el-button>
|
|
|
|
|
+ <el-button link class="link-delete" @click.stop="handleDelete(row)">删除</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <pagination
|
|
|
|
|
+ v-show="total > 0"
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ :page-sizes="pageSizes"
|
|
|
|
|
+ v-model:page="queryParams.pageNum"
|
|
|
|
|
+ v-model:limit="queryParams.pageSize"
|
|
|
|
|
+ @pagination="getList"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-card>
|
|
|
|
|
|
|
|
<!-- 设备详情弹窗 -->
|
|
<!-- 设备详情弹窗 -->
|
|
|
<el-dialog title="设备详情" v-model="detailOpen" width="800px" append-to-body class="detail-dialog">
|
|
<el-dialog title="设备详情" v-model="detailOpen" width="800px" append-to-body class="detail-dialog">
|
|
@@ -177,15 +223,15 @@
|
|
|
</template>
|
|
</template>
|
|
|
<el-empty v-else description="未选中设备" />
|
|
<el-empty v-else description="未选中设备" />
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
- <div class="dialog-footer">
|
|
|
|
|
|
|
+ <div class="dlg-footer">
|
|
|
|
|
+ <el-button class="btn-ghost" @click="detailOpen = false">关 闭</el-button>
|
|
|
<el-button type="primary" icon="Edit" @click="handleUpdate(selectedDetail.equipmentInfo)">编辑设备</el-button>
|
|
<el-button type="primary" icon="Edit" @click="handleUpdate(selectedDetail.equipmentInfo)">编辑设备</el-button>
|
|
|
- <el-button @click="detailOpen = false">关 闭</el-button>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
<!-- 新增/修改弹窗 -->
|
|
<!-- 新增/修改弹窗 -->
|
|
|
- <el-dialog :title="title" v-model="open" width="860px" append-to-body destroy-on-close>
|
|
|
|
|
|
|
+ <el-dialog :title="title" v-model="open" width="860px" append-to-body>
|
|
|
<el-form ref="equipmentRef" :model="form" :rules="rules" label-width="110px">
|
|
<el-form ref="equipmentRef" :model="form" :rules="rules" label-width="110px">
|
|
|
<div class="form-section-title">基础信息</div>
|
|
<div class="form-section-title">基础信息</div>
|
|
|
<el-row :gutter="16">
|
|
<el-row :gutter="16">
|
|
@@ -253,12 +299,12 @@
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
|
<el-form-item label="资产价值(元)" prop="assetValue">
|
|
<el-form-item label="资产价值(元)" prop="assetValue">
|
|
|
- <el-input-number v-model="form.assetValue" :precision="2" :step="100" controls-position="right" style="width: 100%" />
|
|
|
|
|
|
|
+ <el-input-number v-model="form.assetValue" :precision="2" :step="100" style="width: 100%" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
|
<el-form-item label="使用年限(年)" prop="useLife">
|
|
<el-form-item label="使用年限(年)" prop="useLife">
|
|
|
- <el-input-number v-model="form.useLife" :precision="0" :step="1" controls-position="right" style="width: 100%" />
|
|
|
|
|
|
|
+ <el-input-number v-model="form.useLife" :precision="0" :step="1" style="width: 100%" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
</el-row>
|
|
</el-row>
|
|
@@ -275,7 +321,7 @@
|
|
|
<div class="coordinate-row">
|
|
<div class="coordinate-row">
|
|
|
<el-input-number v-model="form.longitude" :precision="6" :step="0.000001" controls-position="right" placeholder="经度" />
|
|
<el-input-number v-model="form.longitude" :precision="6" :step="0.000001" controls-position="right" placeholder="经度" />
|
|
|
<el-input-number v-model="form.latitude" :precision="6" :step="0.000001" controls-position="right" placeholder="纬度" />
|
|
<el-input-number v-model="form.latitude" :precision="6" :step="0.000001" controls-position="right" placeholder="纬度" />
|
|
|
- <el-button type="primary" plain icon="Location" @click="openMapPicker">地图选点</el-button>
|
|
|
|
|
|
|
+ <el-button type="primary" plain @click="openMapPicker">地图选点</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="coordinate-tip">{{ locationTip || "点击地图获取经纬度,地址将自动回填到安装位置,也可以直接手工修改。" }}</div>
|
|
<div class="coordinate-tip">{{ locationTip || "点击地图获取经纬度,地址将自动回填到安装位置,也可以直接手工修改。" }}</div>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
@@ -308,7 +354,20 @@
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="24">
|
|
<el-col :span="24">
|
|
|
<el-form-item label="设备图片" prop="equipmentImage">
|
|
<el-form-item label="设备图片" prop="equipmentImage">
|
|
|
- <image-upload v-model="form.equipmentImage" :limit="1" />
|
|
|
|
|
|
|
+ <el-upload
|
|
|
|
|
+ class="device-image-uploader"
|
|
|
|
|
+ :class="{ 'hide-uploader': imageFileList.length >= 1 }"
|
|
|
|
|
+ list-type="picture-card"
|
|
|
|
|
+ accept="image/*"
|
|
|
|
|
+ :http-request="uploadSingleImage"
|
|
|
|
|
+ :file-list="imageFileList"
|
|
|
|
|
+ :before-upload="beforeImageUpload"
|
|
|
|
|
+ :on-remove="handleRemoveImage"
|
|
|
|
|
+ :limit="1"
|
|
|
|
|
+ :on-exceed="handleImageExceed"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon><plus /></el-icon>
|
|
|
|
|
+ </el-upload>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
</el-row>
|
|
</el-row>
|
|
@@ -339,17 +398,15 @@
|
|
|
解析地址:{{ form.equipmentLocation || "点击地图后自动解析" }}
|
|
解析地址:{{ form.equipmentLocation || "点击地图后自动解析" }}
|
|
|
</div>
|
|
</div>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
- <div class="dialog-footer">
|
|
|
|
|
- <el-button type="primary" @click="mapOpen = false">使用当前坐标</el-button>
|
|
|
|
|
- <el-button @click="mapOpen = false">关 闭</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <el-button @click="mapOpen = false">关 闭</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="mapOpen = false">使用当前坐标</el-button>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
|
|
|
|
|
|
|
+import { computed, getCurrentInstance, onBeforeUnmount, onMounted, reactive, ref, toRefs } from "vue";
|
|
|
import GisMapCanvas from "@/components/GisMapCanvas.vue";
|
|
import GisMapCanvas from "@/components/GisMapCanvas.vue";
|
|
|
import { bd09ToWgs84 } from "@/utils/coordTransform";
|
|
import { bd09ToWgs84 } from "@/utils/coordTransform";
|
|
|
import { useRoute } from "vue-router";
|
|
import { useRoute } from "vue-router";
|
|
@@ -364,8 +421,8 @@ import {
|
|
|
updateEquipment,
|
|
updateEquipment,
|
|
|
updateEquipmentStatus
|
|
updateEquipmentStatus
|
|
|
} from "@/api/pipeNetwork/pdeviceInform";
|
|
} from "@/api/pipeNetwork/pdeviceInform";
|
|
|
-
|
|
|
|
|
-defineOptions({ name: "EquipmentBase" });
|
|
|
|
|
|
|
+import request from "@/utils/request";
|
|
|
|
|
+import { buildMinioAttachmentUrl } from "@/utils/minioAttachment";
|
|
|
|
|
|
|
|
const { proxy } = getCurrentInstance();
|
|
const { proxy } = getCurrentInstance();
|
|
|
|
|
|
|
@@ -375,19 +432,18 @@ const rootTypeId = ref(route.query.rootTypeId ? Number(route.query.rootTypeId) :
|
|
|
|
|
|
|
|
const equipmentList = ref([]);
|
|
const equipmentList = ref([]);
|
|
|
const allTypeList = ref([]);
|
|
const allTypeList = ref([]);
|
|
|
|
|
+const selectedCategoryId = ref();
|
|
|
const loading = ref(false);
|
|
const loading = ref(false);
|
|
|
-const showSearch = ref(true);
|
|
|
|
|
const open = ref(false);
|
|
const open = ref(false);
|
|
|
const detailOpen = ref(false);
|
|
const detailOpen = ref(false);
|
|
|
const mapOpen = ref(false);
|
|
const mapOpen = ref(false);
|
|
|
-// 运行状态筛选:'' 不限 / online 在线 / offline 离线 / alarm 报警
|
|
|
|
|
-// 后端把在线状态与报警状态分成两个参数,这里收在一个下拉里,避免并排放两个控件互相冲突
|
|
|
|
|
-const runStatus = ref("");
|
|
|
|
|
|
|
+const alarmOnly = ref(false);
|
|
|
const mapCenter = ref({ lng: 110.3965, lat: 28.4638 });
|
|
const mapCenter = ref({ lng: 110.3965, lat: 28.4638 });
|
|
|
const mapPoints = ref([]);
|
|
const mapPoints = ref([]);
|
|
|
const total = ref(0);
|
|
const total = ref(0);
|
|
|
const title = ref("");
|
|
const title = ref("");
|
|
|
const selectedDetail = ref({});
|
|
const selectedDetail = ref({});
|
|
|
|
|
+const selectedId = ref("");
|
|
|
const locationTip = ref("");
|
|
const locationTip = ref("");
|
|
|
|
|
|
|
|
const statistics = reactive({
|
|
const statistics = reactive({
|
|
@@ -428,6 +484,14 @@ function getRowStatus(equipmentId) {
|
|
|
return equipmentList.value.find(item => item.equipmentId === equipmentId)?.currentStatus;
|
|
return equipmentList.value.find(item => item.equipmentId === equipmentId)?.currentStatus;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function getRowOnline(equipmentId) {
|
|
|
|
|
+ return equipmentList.value.find(item => item.equipmentId === equipmentId)?.onlineStatus;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getRowAlarm(equipmentId) {
|
|
|
|
|
+ return equipmentList.value.find(item => item.equipmentId === equipmentId)?.alarmStatus;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// parentId 为 0(或空)表示父类
|
|
// parentId 为 0(或空)表示父类
|
|
|
function isTopLevelType(item) {
|
|
function isTopLevelType(item) {
|
|
|
const parentId = item?.parentId;
|
|
const parentId = item?.parentId;
|
|
@@ -495,20 +559,27 @@ const equipmentTypeTree = computed(() => {
|
|
|
return branch ? [branch] : tree;
|
|
return branch ? [branch] : tree;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-// 复用真实类型树;若存在更深层级,将可选叶子平铺在类型下拉里。
|
|
|
|
|
|
|
+const selectedCategory = computed(() => equipmentTypeTree.value.find((item) => item.id === selectedCategoryId.value));
|
|
|
|
|
+
|
|
|
|
|
+// 复用真实类型树;若存在更深层级,将可选叶子平铺在二级区域。
|
|
|
function collectSelectableTypes(nodes) {
|
|
function collectSelectableTypes(nodes) {
|
|
|
return nodes.flatMap((node) => node.children?.length
|
|
return nodes.flatMap((node) => node.children?.length
|
|
|
? collectSelectableTypes(node.children)
|
|
? collectSelectableTypes(node.children)
|
|
|
: node.disabled ? [] : [node]);
|
|
: node.disabled ? [] : [node]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * 可选的设备类型
|
|
|
|
|
- *
|
|
|
|
|
- * equipmentTypeTree 已按菜单注入的 rootTypeId 收窄到本子系统(排水只有 6 个叶子类型),
|
|
|
|
|
- * 数量不多,直接平铺成下拉选项。
|
|
|
|
|
- */
|
|
|
|
|
-const equipmentSubtypes = computed(() => collectSelectableTypes(equipmentTypeTree.value));
|
|
|
|
|
|
|
+const equipmentSubtypes = computed(() => collectSelectableTypes(selectedCategory.value?.children || []));
|
|
|
|
|
+
|
|
|
|
|
+function selectCategory(id) {
|
|
|
|
|
+ selectedCategoryId.value = id;
|
|
|
|
|
+ queryParams.value.equipmentTypeId = undefined;
|
|
|
|
|
+ handleQuery();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function selectEquipmentType(id) {
|
|
|
|
|
+ queryParams.value.equipmentTypeId = id;
|
|
|
|
|
+ handleQuery();
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
function currentStatusMeta(value) {
|
|
function currentStatusMeta(value) {
|
|
|
const map = {
|
|
const map = {
|
|
@@ -519,13 +590,64 @@ function currentStatusMeta(value) {
|
|
|
return map[value] || { text: "未知", type: "info" };
|
|
return map[value] || { text: "未知", type: "info" };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
|
|
|
|
-
|
|
|
|
|
|
|
+// 详情弹窗图片回显统一走 MinIO 前缀(与上传组件一致),不能用 baseUrl(/dev-api)
|
|
|
function resolveImageUrl(url) {
|
|
function resolveImageUrl(url) {
|
|
|
- if (!url) return "";
|
|
|
|
|
- const value = String(url).trim();
|
|
|
|
|
- if (/^(https?:)?\/\//i.test(value) || value.startsWith("data:")) return value;
|
|
|
|
|
- return baseUrl + (value.startsWith("/") ? value : "/" + value);
|
|
|
|
|
|
|
+ return buildMinioAttachmentUrl(url);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 设备图片:单文件上传,调用 /minio/upload?dir=other
|
|
|
|
|
+const imageFileList = ref([]);
|
|
|
|
|
+
|
|
|
|
|
+function beforeImageUpload(file) {
|
|
|
|
|
+ const isImg = file.type.startsWith("image/");
|
|
|
|
|
+ if (!isImg) {
|
|
|
|
|
+ proxy.$modal.msgError("请上传图片格式文件");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const isLt = file.size / 1024 / 1024 < 5;
|
|
|
|
|
+ if (!isLt) {
|
|
|
|
|
+ proxy.$modal.msgError("上传图片大小不能超过 5MB");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleImageExceed() {
|
|
|
|
|
+ proxy.$modal.msgError("只能上传一张设备图片");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function uploadSingleImage(options) {
|
|
|
|
|
+ const formData = new FormData();
|
|
|
|
|
+ formData.append("file", options.file);
|
|
|
|
|
+ return request({
|
|
|
|
|
+ url: "/minio/upload?dir=other",
|
|
|
|
|
+ method: "post",
|
|
|
|
|
+ data: formData,
|
|
|
|
|
+ headers: { repeatSubmit: false },
|
|
|
|
|
+ timeout: 60000
|
|
|
|
|
+ })
|
|
|
|
|
+ .then((res) => {
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ const relativePath = res.relativePath || res.url;
|
|
|
|
|
+ form.value.equipmentImage = relativePath;
|
|
|
|
|
+ imageFileList.value = [
|
|
|
|
|
+ { name: res.originalFilename || "设备图片", url: buildMinioAttachmentUrl(relativePath), status: "success" }
|
|
|
|
|
+ ];
|
|
|
|
|
+ options.onSuccess(res);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ proxy.$modal.msgError(res.msg || "上传失败");
|
|
|
|
|
+ options.onError();
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((err) => {
|
|
|
|
|
+ proxy.$modal.msgError("上传失败");
|
|
|
|
|
+ options.onError(err);
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleRemoveImage() {
|
|
|
|
|
+ form.value.equipmentImage = undefined;
|
|
|
|
|
+ imageFileList.value = [];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const data = reactive({
|
|
const data = reactive({
|
|
@@ -534,7 +656,8 @@ const data = reactive({
|
|
|
pageSize: 10,
|
|
pageSize: 10,
|
|
|
equipmentCode: undefined,
|
|
equipmentCode: undefined,
|
|
|
equipmentName: undefined,
|
|
equipmentName: undefined,
|
|
|
- equipmentTypeId: undefined
|
|
|
|
|
|
|
+ equipmentTypeId: undefined,
|
|
|
|
|
+ onlineStatus: undefined
|
|
|
},
|
|
},
|
|
|
form: {},
|
|
form: {},
|
|
|
rules: {
|
|
rules: {
|
|
@@ -572,15 +695,41 @@ function createEmptyForm() {
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 卡片网格自适应分页:每页条数 = 每行列数 × 固定行数,随屏幕宽度变化,
|
|
|
|
|
+// 保证任意宽度下卡片都能铺满整行
|
|
|
|
|
+const gridRef = ref(null);
|
|
|
|
|
+const PAGE_ROWS = 3;
|
|
|
|
|
+const CARD_MIN_WIDTH = 320;
|
|
|
|
|
+const GRID_GAP = 10;
|
|
|
|
|
+let lastCols = null;
|
|
|
|
|
+
|
|
|
|
|
+function gridColumns() {
|
|
|
|
|
+ const width = gridRef.value?.clientWidth || 0;
|
|
|
|
|
+ if (!width) return null;
|
|
|
|
|
+ return Math.max(1, Math.floor((width + GRID_GAP) / (CARD_MIN_WIDTH + GRID_GAP)));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const pageSizes = computed(() => {
|
|
|
|
|
+ const options = [3, 6, 9, 12, 15, 18];
|
|
|
|
|
+ const current = queryParams.value.pageSize;
|
|
|
|
|
+ return options.includes(current) ? options : [...options, current].sort((a, b) => a - b);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
function getList() {
|
|
function getList() {
|
|
|
|
|
+ const cols = gridColumns();
|
|
|
|
|
+ if (cols && cols !== lastCols) {
|
|
|
|
|
+ lastCols = cols;
|
|
|
|
|
+ queryParams.value.pageSize = cols * PAGE_ROWS;
|
|
|
|
|
+ queryParams.value.pageNum = 1;
|
|
|
|
|
+ }
|
|
|
loading.value = true;
|
|
loading.value = true;
|
|
|
- const status = runStatus.value;
|
|
|
|
|
return listEquipment({
|
|
return listEquipment({
|
|
|
...queryParams.value,
|
|
...queryParams.value,
|
|
|
rootTypeId: rootTypeId.value,
|
|
rootTypeId: rootTypeId.value,
|
|
|
- // 在线 / 离线走 onlineStatus,报警走 alarmStatus,两者互斥,不会同时传
|
|
|
|
|
- onlineStatus: status === "online" ? 1 : status === "offline" ? 0 : undefined,
|
|
|
|
|
- alarmStatus: status === "alarm" ? 1 : undefined
|
|
|
|
|
|
|
+ // 保留菜单的数据范围,大类筛选通过子类型集合交由后端分页过滤。
|
|
|
|
|
+ // 空大类使用无效 ID 返回空结果,避免空参数被后端解释为不限类型。
|
|
|
|
|
+ equipmentTypeIds: selectedCategory.value ? equipmentSubtypes.value.map((item) => item.id).join(",") || "-1" : undefined,
|
|
|
|
|
+ alarmStatus: alarmOnly.value ? 1 : undefined
|
|
|
})
|
|
})
|
|
|
.then((response) => {
|
|
.then((response) => {
|
|
|
equipmentList.value = response.records || [];
|
|
equipmentList.value = response.records || [];
|
|
@@ -609,6 +758,7 @@ function loadStatistics() {
|
|
|
|
|
|
|
|
function reset() {
|
|
function reset() {
|
|
|
form.value = createEmptyForm();
|
|
form.value = createEmptyForm();
|
|
|
|
|
+ imageFileList.value = [];
|
|
|
locationTip.value = "";
|
|
locationTip.value = "";
|
|
|
proxy.resetForm("equipmentRef");
|
|
proxy.resetForm("equipmentRef");
|
|
|
}
|
|
}
|
|
@@ -685,8 +835,9 @@ function handleQuery() {
|
|
|
|
|
|
|
|
function resetQuery() {
|
|
function resetQuery() {
|
|
|
proxy.resetForm("queryRef");
|
|
proxy.resetForm("queryRef");
|
|
|
|
|
+ selectedCategoryId.value = undefined;
|
|
|
queryParams.value.equipmentTypeId = undefined;
|
|
queryParams.value.equipmentTypeId = undefined;
|
|
|
- runStatus.value = "";
|
|
|
|
|
|
|
+ alarmOnly.value = false;
|
|
|
handleQuery();
|
|
handleQuery();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -712,6 +863,9 @@ function handleUpdate(row) {
|
|
|
? Number(form.value.equipmentTypeId)
|
|
? Number(form.value.equipmentTypeId)
|
|
|
: undefined;
|
|
: undefined;
|
|
|
form.value.currentStatus = getRowStatus(equipmentId) ?? 0;
|
|
form.value.currentStatus = getRowStatus(equipmentId) ?? 0;
|
|
|
|
|
+ imageFileList.value = form.value.equipmentImage
|
|
|
|
|
+ ? [{ name: "设备图片", url: buildMinioAttachmentUrl(form.value.equipmentImage), status: "success" }]
|
|
|
|
|
+ : [];
|
|
|
open.value = true;
|
|
open.value = true;
|
|
|
title.value = "修改设备";
|
|
title.value = "修改设备";
|
|
|
});
|
|
});
|
|
@@ -721,6 +875,7 @@ function handleView(row) {
|
|
|
if (!row?.equipmentId) {
|
|
if (!row?.equipmentId) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
+ selectedId.value = row.equipmentId;
|
|
|
return getEquipmentDetail(row.equipmentId).then((response) => {
|
|
return getEquipmentDetail(row.equipmentId).then((response) => {
|
|
|
selectedDetail.value = response.data || {};
|
|
selectedDetail.value = response.data || {};
|
|
|
detailOpen.value = true;
|
|
detailOpen.value = true;
|
|
@@ -738,6 +893,7 @@ function handleDelete(row) {
|
|
|
proxy.$modal.msgSuccess("删除成功");
|
|
proxy.$modal.msgSuccess("删除成功");
|
|
|
if (selectedDetail.value?.equipmentInfo?.equipmentId === row.equipmentId) {
|
|
if (selectedDetail.value?.equipmentInfo?.equipmentId === row.equipmentId) {
|
|
|
selectedDetail.value = {};
|
|
selectedDetail.value = {};
|
|
|
|
|
+ selectedId.value = "";
|
|
|
detailOpen.value = false;
|
|
detailOpen.value = false;
|
|
|
}
|
|
}
|
|
|
return Promise.all([getList(), loadAssistData(), loadStatistics()]);
|
|
return Promise.all([getList(), loadAssistData(), loadStatistics()]);
|
|
@@ -774,18 +930,56 @@ function submitForm() {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+let gridResizeObserver = null;
|
|
|
|
|
+
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
reset();
|
|
reset();
|
|
|
Promise.all([getList(), loadAssistData(), loadStatistics()]);
|
|
Promise.all([getList(), loadAssistData(), loadStatistics()]);
|
|
|
|
|
+ if (typeof ResizeObserver !== "undefined" && gridRef.value) {
|
|
|
|
|
+ gridResizeObserver = new ResizeObserver(() => {
|
|
|
|
|
+ if (gridColumns() !== lastCols) {
|
|
|
|
|
+ getList();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ gridResizeObserver.observe(gridRef.value);
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+onBeforeUnmount(() => {
|
|
|
|
|
+ gridResizeObserver?.disconnect();
|
|
|
|
|
+ gridResizeObserver = null;
|
|
|
});
|
|
});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
|
|
+.equipment-page {
|
|
|
|
|
+ background: #f9f9f9;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 页头 */
|
|
|
|
|
+.page-head {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-title {
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #0a1628;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.btn-primary :deep(.el-icon) {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/* KPI */
|
|
/* KPI */
|
|
|
.kpi-row {
|
|
.kpi-row {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
gap: 16px;
|
|
gap: 16px;
|
|
|
- margin-bottom: 16px;
|
|
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.kpi {
|
|
.kpi {
|
|
@@ -846,38 +1040,217 @@ onMounted(() => {
|
|
|
color: #0a1628;
|
|
color: #0a1628;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 详情弹窗 */
|
|
|
|
|
-.dlg-summary {
|
|
|
|
|
|
|
+/* 台账卡片 */
|
|
|
|
|
+.filter-card {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.section-block-title {
|
|
|
|
|
+ margin: 20px 0 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.ledger {
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.ledger :deep(.el-card__body) {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 筛选栏 */
|
|
|
|
|
+.type-filter {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-filter-title,
|
|
|
|
|
+.type-filter-label {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- gap: 12px;
|
|
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-filter-title {
|
|
|
|
|
+ color: #4e5969;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-filter-title::before,
|
|
|
|
|
+.type-filter-label::before {
|
|
|
|
|
+ content: "";
|
|
|
|
|
+ width: 3px;
|
|
|
|
|
+ height: 14px;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+ background: var(--el-color-primary);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-filter-label {
|
|
|
|
|
+ margin: 16px 0 8px;
|
|
|
|
|
+ color: #86909c;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-filter-label::before {
|
|
|
|
|
+ background: #86909c;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-filter-options {
|
|
|
|
|
+ display: flex;
|
|
|
flex-wrap: wrap;
|
|
flex-wrap: wrap;
|
|
|
- padding: 14px;
|
|
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-category,
|
|
|
|
|
+.type-chip {
|
|
|
|
|
+ padding: 0 16px;
|
|
|
|
|
+ height: 34px;
|
|
|
|
|
+ border: 1px solid #e5e6eb;
|
|
|
border-radius: 8px;
|
|
border-radius: 8px;
|
|
|
- background: #f5f9ff;
|
|
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ color: #4e5969;
|
|
|
|
|
+ font: inherit;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: background .15s, color .15s, border-color .15s;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.summary-name {
|
|
|
|
|
- font-size: 15px;
|
|
|
|
|
|
|
+.type-category:hover,
|
|
|
|
|
+.type-chip:hover {
|
|
|
|
|
+ color: var(--el-color-primary);
|
|
|
|
|
+ border-color: var(--el-color-primary-light-5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-category[aria-pressed="true"] {
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ border-color: var(--el-color-primary);
|
|
|
|
|
+ background: var(--el-color-primary);
|
|
|
font-weight: 500;
|
|
font-weight: 500;
|
|
|
- color: #014db2;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.summary-code {
|
|
|
|
|
|
|
+.type-chip {
|
|
|
|
|
+ height: 30px;
|
|
|
|
|
+ padding: 0 14px;
|
|
|
|
|
+ border-color: transparent;
|
|
|
|
|
+ border-radius: 999px;
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-chip:hover,
|
|
|
|
|
+.type-chip[aria-pressed="true"] {
|
|
|
|
|
+ background: var(--el-color-primary-light-9);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-chip[aria-pressed="true"] {
|
|
|
|
|
+ color: var(--el-color-primary);
|
|
|
|
|
+ border-color: var(--el-color-primary);
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-category:focus-visible,
|
|
|
|
|
+.type-chip:focus-visible {
|
|
|
|
|
+ outline: 2px solid var(--el-color-primary);
|
|
|
|
|
+ outline-offset: 2px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filters {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 4px 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filters :deep(.el-form-item) {
|
|
|
|
|
+ margin-right: 0;
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filters :deep(.el-form-item__label) {
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
font-weight: 400;
|
|
font-weight: 400;
|
|
|
color: #6b7280;
|
|
color: #6b7280;
|
|
|
|
|
+ padding-right: 8px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.summary-tag {
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- padding: 3px 8px;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
|
|
+.input--code,
|
|
|
|
|
+.input--name {
|
|
|
|
|
+ width: 150px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.select--online {
|
|
|
|
|
+ width: 120px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.btn-query {
|
|
|
|
|
+ height: 34px;
|
|
|
|
|
+ padding: 0 16px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.btn-reset {
|
|
|
|
|
+ height: 34px;
|
|
|
|
|
+ padding: 0 16px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 设备卡片 */
|
|
|
|
|
+.grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
|
|
|
+ align-items: start;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ min-height: 160px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ align-self: start;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ padding: 10px 12px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
background: #fff;
|
|
background: #fff;
|
|
|
- font-size: 11px;
|
|
|
|
|
|
|
+ border: 1px solid #ebebed;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card:hover {
|
|
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card.is-selected {
|
|
|
|
|
+ background: #f5f9ff;
|
|
|
|
|
+ border: 1.5px solid #014db2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card.is-alarm {
|
|
|
|
|
+ border: 1.5px solid #fca5a5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-top {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-name {
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ font-size: 14px;
|
|
|
font-weight: 500;
|
|
font-weight: 500;
|
|
|
- color: #014db2;
|
|
|
|
|
|
|
+ color: #0a1628;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.status-group {
|
|
.status-group {
|
|
@@ -930,6 +1303,93 @@ onMounted(() => {
|
|
|
color: #ef4444;
|
|
color: #ef4444;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.card-code {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+ color: #9ca3af;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-district {
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ color: #6b7280;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-bottom {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-tag {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 3px 8px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ background: #edf3fc;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #014db2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-actions :deep(.el-button) {
|
|
|
|
|
+ height: auto;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.link-detail {
|
|
|
|
|
+ color: #014db2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.link-delete {
|
|
|
|
|
+ color: #ef4444;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 详情弹窗 */
|
|
|
|
|
+.dlg-summary {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ padding: 14px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #f5f9ff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-name {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #014db2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-code {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+ color: #6b7280;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.summary-tag {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 3px 8px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #014db2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.dlg-grid {
|
|
.dlg-grid {
|
|
|
margin-top: 16px;
|
|
margin-top: 16px;
|
|
|
}
|
|
}
|
|
@@ -996,6 +1456,7 @@ onMounted(() => {
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.dlg-footer,
|
|
|
.dialog-footer {
|
|
.dialog-footer {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
@@ -1017,5 +1478,15 @@ onMounted(() => {
|
|
|
.kpi {
|
|
.kpi {
|
|
|
flex: 1 1 100%;
|
|
flex: 1 1 100%;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ .page-head {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.device-image-uploader.hide-uploader :deep(.el-upload--picture-card) {
|
|
|
|
|
+ display: none;
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|