You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1546 lines
50 KiB
1546 lines
50 KiB
<template> |
|
<div class="device-manage"> |
|
<!-- 搜索筛选区域 --> |
|
<div class="search-filter-area"> |
|
<div class="search-group"> |
|
<div class="search-item"> |
|
<label>设备名称</label> |
|
<el-input |
|
v-model="searchForm.deviceName" |
|
placeholder="请输入水泵/仪表/网关名称或编号" |
|
clearable |
|
class="custom-input" |
|
@clear="handleSearch" |
|
> |
|
<template #prefix> |
|
<svg class="search-icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> |
|
<path d="M909.6 854.5L649.9 594.8c41.3-54.3 65.9-121.4 65.9-194.1C715.8 262.2 560.6 107 370.4 107S25 262.2 25 452.4s155.2 345.4 345.4 345.4c72.7 0 139.8-24.6 194.1-65.9l259.7 259.7c11.1 11.1 29.1 11.1 40.2 0l45.2-45.2c11.1-11.1 11.1-29.1 0-40.2zM370.4 705c-139.1 0-252.6-113.5-252.6-252.6s113.5-252.6 252.6-252.6 252.6 113.5 252.6 252.6-113.5 252.6-252.6 252.6z" fill="currentColor"/> |
|
</svg> |
|
</template> |
|
</el-input> |
|
</div> |
|
<div class="search-item"> |
|
<label>设备状态</label> |
|
<el-select |
|
v-model="searchForm.deviceStatus" |
|
placeholder="全部" |
|
clearable |
|
class="custom-select" |
|
@change="handleSearch" |
|
> |
|
<el-option label="全部" value=""></el-option> |
|
<el-option label="运行中" value="running"></el-option> |
|
<el-option label="离线" value="offline"></el-option> |
|
<el-option label="故障" value="fault"></el-option> |
|
</el-select> |
|
</div> |
|
<div class="search-item"> |
|
<label>设备子类型</label> |
|
<el-select |
|
v-model="searchForm.deviceType" |
|
placeholder="全部" |
|
clearable |
|
class="custom-select" |
|
@change="handleSearch" |
|
> |
|
<el-option label="全部" value=""></el-option> |
|
<el-option |
|
v-for="type in getSubDeviceTypes()" |
|
:key="type.value" |
|
:label="type.label" |
|
:value="type.value" |
|
></el-option> |
|
</el-select> |
|
</div> |
|
</div> |
|
<div class="action-group"> |
|
<div class="primary-btn"><el-button type="primary" @click="handleSearch" class="custom-btn">查询</el-button></div> |
|
<el-button @click="handleReset" class="custom-btn">重置</el-button> |
|
<div class="success-btn"><el-button type="success" @click="handleExport" class="custom-btn"> |
|
<svg class="btn-icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> |
|
<path d="M384 704h256v-192h192l-320-320-320 320h192v192zM128 768h768v64H128v-64z" fill="currentColor"/> |
|
</svg> |
|
导出列表 |
|
</el-button></div> |
|
</div> |
|
</div> |
|
|
|
<!-- 设备统计卡片 --> |
|
<div class="device-stats"> |
|
<div class="stat-card total"> |
|
<div class="stat-icon"> |
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> |
|
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/> |
|
<path d="M688 464H336c-8.8 0-16 7.2-16 16v64c0 8.8 7.2 16 16 16h352c8.8 0 16-7.2 16-16v-64c0-8.8-7.2-16-16-16z" fill="currentColor"/> |
|
</svg> |
|
</div> |
|
<div class="stat-info"> |
|
<div class="stat-value">{{ deviceStats.total }}</div> |
|
<div class="stat-label">设备总数</div> |
|
</div> |
|
</div> |
|
<div class="stat-card running"> |
|
<div class="stat-icon"> |
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> |
|
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/> |
|
<path d="M672 464H352c-8.8 0-16 7.2-16 16v64c0 8.8 7.2 16 16 16h320c8.8 0 16-7.2 16-16v-64c0-8.8-7.2-16-16-16z" fill="currentColor"/> |
|
<path d="M512 592c-4.4 0-8 3.6-8 8v256c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V600c0-4.4-3.6-8-8-8h-48z" fill="currentColor"/> |
|
</svg> |
|
</div> |
|
<div class="stat-info"> |
|
<div class="stat-value">{{ deviceStats.running }}</div> |
|
<div class="stat-label">运行中</div> |
|
</div> |
|
</div> |
|
<div class="stat-card offline"> |
|
<div class="stat-icon"> |
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> |
|
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/> |
|
<path d="M512 352c-8.8 0-16 7.2-16 16v224c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16V368c0-8.8-7.2-16-16-16h-48z" fill="currentColor"/> |
|
</svg> |
|
</div> |
|
<div class="stat-info"> |
|
<div class="stat-value">{{ deviceStats.offline }}</div> |
|
<div class="stat-label">离线</div> |
|
</div> |
|
</div> |
|
<div class="stat-card fault"> |
|
<div class="stat-icon"> |
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> |
|
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/> |
|
<path d="M464 336a48 48 0 1096 0 48 48 0 10-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z" fill="currentColor"/> |
|
</svg> |
|
</div> |
|
<div class="stat-info"> |
|
<div class="stat-value">{{ deviceStats.fault }}</div> |
|
<div class="stat-label">故障</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- 设备分类标签页 --> |
|
<div class="category-tabs"> |
|
<div |
|
v-for="(category, index) in deviceCategories" |
|
:key="index" |
|
:class="['category-tab', { active: activeCategory === index }]" |
|
@click="handleCategoryChange(index)" |
|
> |
|
<span class="tab-icon">{{ getCategoryIcon(category.value) }}</span> |
|
<span class="tab-label">{{ category.label }}</span> |
|
<span class="tab-count">({{ category.count }})</span> |
|
</div> |
|
</div> |
|
|
|
<!-- 设备列表 --> |
|
<div class="device-list-container"> |
|
<div class="list-header"> |
|
<div class="list-title">设备列表</div> |
|
<div class="list-info"> |
|
共 {{ filteredDevices.length }} 台设备 |
|
</div> |
|
</div> |
|
<div class="device-grid"> |
|
<div |
|
v-for="(device, index) in paginatedDevices" |
|
:key="index" |
|
:class="['device-card', device.status]" |
|
@click="viewDeviceDetail(device)" |
|
> |
|
<div class="card-header"> |
|
<div class="device-icon" :style="{ background: device.iconBg }"> |
|
<span class="icon-emoji">{{ getDeviceIconEmoji(device.type) }}</span> |
|
</div> |
|
<div class="device-header-info"> |
|
<div class="device-name">{{ device.name }}</div> |
|
<div class="device-code">{{ device.code }}</div> |
|
</div> |
|
<div class="status-badge" :class="device.status"> |
|
{{ getStatusText(device.status) }} |
|
</div> |
|
</div> |
|
<div class="card-body"> |
|
<div class="device-info-row"> |
|
<span class="info-label">型号:</span> |
|
<span class="info-value">{{ device.model }}</span> |
|
</div> |
|
<div class="device-info-row"> |
|
<span class="info-label">安装日期:</span> |
|
<span class="info-value">{{ device.installDate }}</span> |
|
</div> |
|
<div class="device-info-row" v-if="device.area"> |
|
<span class="info-label">所在区域:</span> |
|
<span class="info-value">{{ device.area }}</span> |
|
</div> |
|
<div class="device-info-row" v-if="device.status === 'running' && device.currentPressure !== undefined"> |
|
<span class="info-label">当前压力:</span> |
|
<span class="info-value highlight">{{ device.currentPressure }} MPa</span> |
|
</div> |
|
</div> |
|
<div class="card-footer"> |
|
<div class="device-params" v-if="device.parameters && device.parameters.length > 0"> |
|
<div |
|
v-for="(param, pIndex) in device.parameters.slice(0, 3)" |
|
:key="pIndex" |
|
class="param-item" |
|
> |
|
<span class="param-label">{{ param.label }}:</span> |
|
<span class="param-value">{{ param.value }}{{ param.unit }}</span> |
|
</div> |
|
<div v-if="device.parameters.length > 3" class="param-more"> |
|
+{{ device.parameters.length - 3 }}项 |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- 分页 --> |
|
<div class="pagination-container"> |
|
<el-pagination |
|
v-model:current-page="currentPage" |
|
v-model:page-size="pageSize" |
|
:page-sizes="[12, 24, 48, 96]" |
|
:total="filteredDevices.length" |
|
layout="total, sizes, prev, pager, next, jumper" |
|
@size-change="handleSizeChange" |
|
@current-change="handleCurrentChange" |
|
class="custom-pagination" |
|
> |
|
</el-pagination> |
|
</div> |
|
</div> |
|
|
|
<!-- 设备详情弹窗 --> |
|
<el-dialog |
|
v-model="detailDialogVisible" |
|
:title="selectedDevice ? selectedDevice.name + ' - 设备详情' : '设备详情'" |
|
width="900px" |
|
class="custom-dialog" |
|
@close="closeDetailDialog" |
|
> |
|
<div class="detail-content" v-if="selectedDevice"> |
|
<div class="detail-section"> |
|
<div class="section-title">基本信息</div> |
|
<div class="info-grid"> |
|
<div class="info-item"> |
|
<span class="item-label">设备编号:</span> |
|
<span class="item-value">{{ selectedDevice.code }}</span> |
|
</div> |
|
<div class="info-item"> |
|
<span class="item-label">设备名称:</span> |
|
<span class="item-value">{{ selectedDevice.name }}</span> |
|
</div> |
|
<div class="info-item"> |
|
<span class="item-label">设备型号:</span> |
|
<span class="item-value">{{ selectedDevice.model }}</span> |
|
</div> |
|
<div class="info-item"> |
|
<span class="item-label">设备类型:</span> |
|
<span class="item-value">{{ getDeviceTypeName(selectedDevice.type) }}</span> |
|
</div> |
|
<div class="info-item"> |
|
<span class="item-label">安装日期:</span> |
|
<span class="item-value">{{ selectedDevice.installDate }}</span> |
|
</div> |
|
<div class="info-item"> |
|
<span class="item-label">设备状态:</span> |
|
<span class="item-value status-badge" :class="selectedDevice.status"> |
|
{{ getStatusText(selectedDevice.status) }} |
|
</span> |
|
</div> |
|
<div class="info-item" v-if="selectedDevice.area"> |
|
<span class="item-label">所在区域:</span> |
|
<span class="item-value">{{ selectedDevice.area }}</span> |
|
</div> |
|
<div class="info-item" v-if="selectedDevice.location"> |
|
<span class="item-label">具体位置:</span> |
|
<span class="item-value">{{ selectedDevice.location }}</span> |
|
</div> |
|
<div class="info-item full-width" v-if="selectedDevice.spec"> |
|
<span class="item-label">设备参数:</span> |
|
<span class="item-value">{{ selectedDevice.spec }}</span> |
|
</div> |
|
<div class="info-item full-width" v-if="selectedDevice.description"> |
|
<span class="item-label">设备描述:</span> |
|
<span class="item-value">{{ selectedDevice.description }}</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="detail-section" v-if="selectedDevice.parameters && selectedDevice.parameters.length > 0"> |
|
<div class="section-title">运行参数</div> |
|
<div class="params-grid"> |
|
<div |
|
v-for="(param, index) in selectedDevice.parameters" |
|
:key="index" |
|
class="param-card" |
|
> |
|
<div class="param-label">{{ param.label }}</div> |
|
<div class="param-value">{{ param.value }}</div> |
|
<div class="param-unit">{{ param.unit }}</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="detail-section"> |
|
<div class="section-title">运行状态</div> |
|
<div class="status-info"> |
|
<div class="status-item"> |
|
<span class="status-label">当前状态:</span> |
|
<span class="status-text" :class="selectedDevice.status"> |
|
{{ getStatusText(selectedDevice.status) }} |
|
</span> |
|
</div> |
|
<div class="status-item" v-if="selectedDevice.status === 'running' && selectedDevice.runtime"> |
|
<span class="status-label">运行时长:</span> |
|
<span class="status-text">{{ selectedDevice.runtime }}</span> |
|
</div> |
|
<div class="status-item" v-if="selectedDevice.status === 'fault'"> |
|
<span class="status-label">故障信息:</span> |
|
<span class="status-text fault">{{ selectedDevice.faultInfo || '未知故障' }}</span> |
|
</div> |
|
<div class="status-item" v-if="selectedDevice.lastUpdate"> |
|
<span class="status-label">最后更新:</span> |
|
<span class="status-text">{{ selectedDevice.lastUpdate }}</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="detail-section" v-if="selectedDevice.type === 'pump'"> |
|
<div class="section-title">控制信息</div> |
|
<div class="control-grid"> |
|
<div class="control-item"> |
|
<span class="control-label">设定压力:</span> |
|
<el-slider v-model="selectedDevice.setPressure" :min="0.2" :max="0.8" :step="0.05" :disabled="selectedDevice.status !== 'running'"></el-slider> |
|
<span class="control-value">{{ selectedDevice.setPressure }} MPa</span> |
|
</div> |
|
<div class="control-item" v-if="selectedDevice.frequency"> |
|
<span class="control-label">运行频率:</span> |
|
<el-slider v-model="selectedDevice.frequency" :min="0" :max="50" :step="0.5" :disabled="selectedDevice.status !== 'running'"></el-slider> |
|
<span class="control-value">{{ selectedDevice.frequency }} Hz</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="detail-section"> |
|
<div class="section-title"> |
|
<span>历史运行数据趋势</span> |
|
<div class="section-actions"> |
|
<el-select v-model="historyTimeRange" @change="updateHistoryChart" size="small" class="custom-select" style="width: 1.2rem;"> |
|
<el-option label="近24小时" value="24h"></el-option> |
|
<el-option label="近7天" value="7d"></el-option> |
|
<el-option label="近30天" value="30d"></el-option> |
|
</el-select> |
|
<el-button size="small" @click="exportHistoryData" class="custom-btn">导出数据</el-button> |
|
</div> |
|
</div> |
|
<div class="chart-container"> |
|
<div ref="historyChart" class="chart"></div> |
|
</div> |
|
</div> |
|
|
|
<div class="detail-section"> |
|
<div class="section-title"> |
|
<span>维护记录</span> |
|
<div class="section-actions"> |
|
<el-button size="small" type="primary" @click="showMaintenanceDialog" class="custom-btn">新增记录</el-button> |
|
<el-button size="small" @click="exportMaintenanceRecords" class="custom-btn">导出记录</el-button> |
|
</div> |
|
</div> |
|
<div class="maintenance-list"> |
|
<div |
|
v-for="(record, index) in maintenanceRecords" |
|
:key="index" |
|
class="maintenance-item" |
|
> |
|
<div class="maintenance-header"> |
|
<div class="maintenance-type" :class="record.type">{{ getMaintenanceTypeText(record.type) }}</div> |
|
<div class="maintenance-date">{{ record.date }}</div> |
|
</div> |
|
<div class="maintenance-content"> |
|
<div class="maintenance-title">{{ record.title }}</div> |
|
<div class="maintenance-desc">{{ record.description }}</div> |
|
<div class="maintenance-operator"> |
|
<span class="operator-label">维护人:</span> |
|
<span class="operator-name">{{ record.operator }}</span> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="no-maintenance" v-if="maintenanceRecords.length === 0"> |
|
<div class="no-data-icon">📋</div> |
|
<div class="no-data-text">暂无维护记录</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<template #footer> |
|
<div class="dialog-footer"> |
|
<el-button @click="closeDetailDialog" class="custom-btn">关闭</el-button> |
|
<el-button type="primary" @click="saveControl" class="custom-btn" v-if="selectedDevice && selectedDevice.type === 'pump'">保存控制</el-button> |
|
</div> |
|
</template> |
|
</el-dialog> |
|
|
|
<!-- 维护记录弹窗 --> |
|
<el-dialog |
|
v-model="maintenanceDialogVisible" |
|
title="新增维护记录" |
|
width="600px" |
|
class="custom-dialog" |
|
> |
|
<el-form :model="maintenanceForm" :rules="maintenanceRules" ref="maintenanceFormRef" label-width="100px" class="custom-form"> |
|
<el-form-item label="维护类型" prop="type"> |
|
<el-select v-model="maintenanceForm.type" placeholder="请选择维护类型" class="custom-select"> |
|
<el-option label="定期保养" value="regular"></el-option> |
|
<el-option label="故障维修" value="repair"></el-option> |
|
<el-option label="预防性维护" value="preventive"></el-option> |
|
<el-option label="升级改造" value="upgrade"></el-option> |
|
</el-select> |
|
</el-form-item> |
|
<el-form-item label="维护标题" prop="title"> |
|
<el-input v-model="maintenanceForm.title" placeholder="请输入维护标题" class="custom-input"></el-input> |
|
</el-form-item> |
|
<el-form-item label="维护描述" prop="description"> |
|
<el-input |
|
v-model="maintenanceForm.description" |
|
type="textarea" |
|
:rows="3" |
|
placeholder="请输入维护描述" |
|
class="custom-input" |
|
></el-input> |
|
</el-form-item> |
|
<el-form-item label="维护人员"> |
|
<el-input v-model="maintenanceForm.operator" placeholder="请输入维护人员姓名" class="custom-input"></el-input> |
|
</el-form-item> |
|
<el-form-item label="维护时间"> |
|
<el-date-picker |
|
v-model="maintenanceForm.date" |
|
type="datetime" |
|
placeholder="选择维护时间" |
|
value-format="yyyy-MM-dd HH:mm:ss" |
|
class="custom-picker" |
|
></el-date-picker> |
|
</el-form-item> |
|
<el-form-item label="维护结果"> |
|
<el-radio-group v-model="maintenanceForm.result" class="custom-radio"> |
|
<el-radio label="success">正常完成</el-radio> |
|
<el-radio label="partial">部分完成</el-radio> |
|
<el-radio label="failed">未完成</el-radio> |
|
</el-radio-group> |
|
</el-form-item> |
|
</el-form> |
|
<template #footer> |
|
<el-button @click="maintenanceDialogVisible = false" class="custom-btn">取消</el-button> |
|
<el-button type="primary" @click="saveMaintenanceRecord" class="custom-btn">保存</el-button> |
|
</template> |
|
</el-dialog> |
|
</div> |
|
</template> |
|
|
|
<script> |
|
import * as echarts from "echarts"; |
|
|
|
export default { |
|
name: "DeviceManage", |
|
data() { |
|
return { |
|
searchForm: { |
|
deviceName: "", |
|
deviceStatus: "", |
|
deviceType: "" |
|
}, |
|
deviceCategories: [ |
|
{ label: "全部设备", value: "all", count: 26 }, |
|
{ label: "水泵组", value: "pump", count: 16 }, |
|
{ label: "仪表", value: "meter", count: 4 }, |
|
{ label: "网关", value: "gateway", count: 3 }, |
|
{ label: "传感器", value: "sensor", count: 3 } |
|
], |
|
activeCategory: 0, |
|
currentPage: 1, |
|
pageSize: 12, |
|
deviceList: [], |
|
deviceStats: { |
|
total: 26, |
|
running: 22, |
|
offline: 3, |
|
fault: 1 |
|
}, |
|
detailDialogVisible: false, |
|
selectedDevice: null, |
|
historyTimeRange: "24h", |
|
historyChart: null, |
|
maintenanceDialogVisible: false, |
|
maintenanceForm: { |
|
type: "regular", |
|
title: "", |
|
description: "", |
|
operator: "", |
|
date: "", |
|
result: "success" |
|
}, |
|
maintenanceRules: { |
|
type: [{ required: true, message: "请选择维护类型", trigger: "change" }], |
|
title: [{ required: true, message: "请输入维护标题", trigger: "blur" }], |
|
description: [{ required: true, message: "请输入维护描述", trigger: "blur" }] |
|
}, |
|
maintenanceRecords: [] |
|
}; |
|
}, |
|
computed: { |
|
filteredDevices() { |
|
let devices = [...this.deviceList]; |
|
const category = this.deviceCategories[this.activeCategory]; |
|
if (category.value !== "all") { |
|
devices = devices.filter(d => d.type === category.value); |
|
} |
|
if (this.searchForm.deviceName) { |
|
const keyword = this.searchForm.deviceName.toLowerCase(); |
|
devices = devices.filter(d => |
|
d.name.toLowerCase().includes(keyword) || |
|
d.code.toLowerCase().includes(keyword) |
|
); |
|
} |
|
if (this.searchForm.deviceStatus) { |
|
devices = devices.filter(d => d.status === this.searchForm.deviceStatus); |
|
} |
|
if (this.searchForm.deviceType) { |
|
devices = devices.filter(d => d.subType === this.searchForm.deviceType); |
|
} |
|
return devices; |
|
}, |
|
paginatedDevices() { |
|
const start = (this.currentPage - 1) * this.pageSize; |
|
const end = start + this.pageSize; |
|
return this.filteredDevices.slice(start, end); |
|
} |
|
}, |
|
mounted() { |
|
this.initDeviceList(); |
|
}, |
|
methods: { |
|
initDeviceList() { |
|
const pumpIcons = ["#FFD700", "#FFA500", "#FF6B6B", "#4ECDC4"]; |
|
const gatewayIcons = ["#667eea", "#764ba2"]; |
|
const meterIcons = ["#f093fb", "#f5576c"]; |
|
const sensorIcons = ["#30cfd0", "#330867"]; |
|
|
|
// 水泵设备 |
|
const pumpTypes = [ |
|
{ label: "高区水泵", value: "highZonePump", zone: "高区" }, |
|
{ label: "中区水泵", value: "middleZonePump", zone: "中区" }, |
|
{ label: "低区水泵", value: "lowZonePump", zone: "低区" }, |
|
{ label: "消防水泵", value: "fireZonePump", zone: "消防区" } |
|
]; |
|
|
|
const pumpModels = [ |
|
{ model: "SBP-45-30", power: 45, pressure: 0.6, flow: 180 }, |
|
{ model: "SBP-37-25", power: 37, pressure: 0.5, flow: 150 }, |
|
{ model: "SBP-22-18", power: 22, pressure: 0.4, flow: 120 }, |
|
{ model: "SBP-55-40", power: 55, pressure: 0.7, flow: 200 } |
|
]; |
|
|
|
for (let i = 1; i <= 16; i++) { |
|
const typeIndex = i % pumpTypes.length; |
|
const modelIndex = i % pumpModels.length; |
|
const iconIndex = i % pumpIcons.length; |
|
|
|
this.deviceList.push({ |
|
code: `WP-${pumpTypes[typeIndex].zone === '高区' ? 'H' : pumpTypes[typeIndex].zone === '中区' ? 'M' : pumpTypes[typeIndex].zone === '低区' ? 'L' : 'F'}-${String(i).padStart(3, '0')}`, |
|
name: `${pumpTypes[typeIndex].zone}${i}#${pumpTypes[typeIndex].label.replace('区', '').replace('消防', '')}`, |
|
type: "pump", |
|
subType: pumpTypes[typeIndex].value, |
|
model: pumpModels[modelIndex].model, |
|
spec: `额定功率: ${pumpModels[modelIndex].power}kW, 额定压力: ${pumpModels[modelIndex].pressure}MPa, 额定流量: ${pumpModels[modelIndex].flow}m³/h`, |
|
installDate: this.getRandomDate(2020, 2023), |
|
status: i <= 14 ? "running" : i <= 15 ? "offline" : "fault", |
|
faultInfo: i > 15 ? "水泵过载" : undefined, |
|
area: pumpTypes[typeIndex].zone, |
|
location: `设备房-${Math.ceil(i / 4)}层`, |
|
description: `${pumpTypes[typeIndex].label},用于${pumpTypes[typeIndex].zone}供水系统`, |
|
iconBg: `linear-gradient(135deg, ${pumpIcons[iconIndex]} 0%, ${pumpIcons[(iconIndex + 1) % pumpIcons.length]} 100%)`, |
|
currentPressure: (pumpModels[modelIndex].pressure * (0.85 + Math.random() * 0.2)).toFixed(2), |
|
currentFlow: (pumpModels[modelIndex].flow * (0.8 + Math.random() * 0.3)).toFixed(1), |
|
currentPower: (pumpModels[modelIndex].power * (0.75 + Math.random() * 0.2)).toFixed(1), |
|
setPressure: pumpModels[modelIndex].pressure, |
|
frequency: (35 + Math.random() * 15).toFixed(1), |
|
runtime: `${Math.floor(1000 + Math.random() * 2000)}小时`, |
|
lastUpdate: this.getRecentTime(), |
|
parameters: [ |
|
{ label: "出口压力", value: (pumpModels[modelIndex].pressure * (0.85 + Math.random() * 0.2)).toFixed(2), unit: "MPa" }, |
|
{ label: "流量", value: (pumpModels[modelIndex].flow * (0.8 + Math.random() * 0.3)).toFixed(1), unit: "m³/h" }, |
|
{ label: "功率", value: (pumpModels[modelIndex].power * (0.75 + Math.random() * 0.2)).toFixed(1), unit: "kW" }, |
|
{ label: "频率", value: (35 + Math.random() * 15).toFixed(1), unit: "Hz" }, |
|
{ label: "电流", value: (pumpModels[modelIndex].power / 380 * (2 + Math.random() * 0.5)).toFixed(2), unit: "A" }, |
|
{ label: "电压", value: (380 + Math.random() * 10).toFixed(1), unit: "V" } |
|
] |
|
}); |
|
} |
|
|
|
// 网关设备 |
|
for (let i = 1; i <= 3; i++) { |
|
const iconIndex = i % gatewayIcons.length; |
|
const areaIndex = i % 4; |
|
|
|
const areas = ["高区", "中区", "低区", "消防区"]; |
|
|
|
this.deviceList.push({ |
|
code: `GW-00${i}`, |
|
name: `${i}号水泵网关`, |
|
type: "gateway", |
|
subType: "pumpGateway", |
|
model: "LGT-GW1000", |
|
spec: "支持50个节点, Modbus/RS485, 供电: 24V/2A", |
|
installDate: this.getRandomDate(2020, 2021), |
|
status: i <= 2 ? "running" : "offline", |
|
area: areas[areaIndex], |
|
location: `${areas[areaIndex]}-机房`, |
|
description: "水泵系统智能网关,负责数据采集和控制指令下发", |
|
iconBg: `linear-gradient(135deg, #${gatewayIcons[iconIndex]} 0%, #${gatewayIcons[(iconIndex + 1) % gatewayIcons.length]} 100%)`, |
|
runtime: `${Math.floor(2000 + Math.random() * 3000)}小时`, |
|
lastUpdate: this.getRecentTime(), |
|
parameters: [ |
|
{ label: "在线设备数", value: `${5 + Math.floor(Math.random() * 6)}`, unit: "台" }, |
|
{ label: "网络延迟", value: `${Math.floor(10 + Math.random() * 20)}`, unit: "ms" }, |
|
{ label: "信号强度", value: `-40 - ${Math.floor(Math.random() * 20)}`, unit: "dBm" }, |
|
{ label: "CPU使用率", value: `${Math.floor(20 + Math.random() * 30)}`, unit: "%" } |
|
] |
|
}); |
|
} |
|
|
|
// 仪表设备(流量计、压力表) |
|
for (let i = 1; i <= 4; i++) { |
|
const iconIndex = i % meterIcons.length; |
|
const areaIndex = i % 4; |
|
const meterTypes = ["流量计", "压力表", "电表", "液位计"]; |
|
|
|
const areas = ["高区", "中区", "低区", "消防区"]; |
|
|
|
this.deviceList.push({ |
|
code: `MT-${String(i).padStart(3, '0')}`, |
|
name: `${areas[areaIndex]}${meterTypes[i-1]}-${i}`, |
|
type: "meter", |
|
subType: `pump${meterTypes[i-1]}`, |
|
model: i === 3 ? "DTS666" : "LGT-MT100", |
|
spec: i === 3 ? "三相四线, 3(6)A, 0.5S级, RS485通信" : "精度±0.5%, 4-20mA/RS485", |
|
installDate: this.getRandomDate(2020, 2021), |
|
status: i <= 3 ? "running" : "offline", |
|
area: areas[areaIndex], |
|
location: `${areas[areaIndex]}-设备房`, |
|
description: `${meterTypes[i-1]},用于${areas[areaIndex]}数据监测`, |
|
iconBg: `linear-gradient(135deg, #${meterIcons[iconIndex]} 0%, #${meterIcons[(iconIndex + 1) % meterIcons.length]} 100%)`, |
|
runtime: `${Math.floor(2000 + Math.random() * 3000)}小时`, |
|
lastUpdate: this.getRecentTime(), |
|
parameters: this.getMeterParameters(i) |
|
}); |
|
} |
|
|
|
// 传感器设备 |
|
const sensorTypes = [ |
|
{ label: "压力传感器", value: "pressureSensor", spec: "量程: 0-1.6MPa, 精度: ±0.25%FS" }, |
|
{ label: "液位传感器", value: "levelSensor", spec: "量程: 0-10m, 精度: ±1%FS" }, |
|
{ label: "流量传感器", value: "flowSensor", spec: "量程: 0-500m³/h, 精度: ±0.5%" } |
|
]; |
|
|
|
for (let i = 1; i <= 3; i++) { |
|
const typeIndex = (i - 1) % sensorTypes.length; |
|
const iconIndex = i % sensorIcons.length; |
|
const areaIndex = i % 4; |
|
|
|
const areas = ["高区", "中区", "低区", "消防区"]; |
|
|
|
this.deviceList.push({ |
|
code: `SN-${String(i).padStart(3, '0')}`, |
|
name: `${areas[areaIndex]}${sensorTypes[typeIndex].label}-${i}`, |
|
type: "sensor", |
|
subType: sensorTypes[typeIndex].value, |
|
model: "LGT-SN200", |
|
spec: sensorTypes[typeIndex].spec, |
|
installDate: this.getRandomDate(2020, 2022), |
|
status: i <= 2 ? "running" : "offline", |
|
area: areas[areaIndex], |
|
location: `${areas[areaIndex]}-水箱房`, |
|
description: `${sensorTypes[typeIndex].label},用于${areas[areaIndex]}监测`, |
|
iconBg: `linear-gradient(135deg, #${sensorIcons[iconIndex]} 0%, #${sensorIcons[(iconIndex + 1) % sensorIcons.length]} 100%)`, |
|
runtime: `${Math.floor(1000 + Math.random() * 2000)}小时`, |
|
lastUpdate: this.getRecentTime(), |
|
parameters: this.getSensorParameters(sensorTypes[typeIndex].value) |
|
}); |
|
} |
|
}, |
|
getSensorParameters(type) { |
|
switch (type) { |
|
case "pressureSensor": |
|
return [ |
|
{ label: "当前压力", value: `${(0.3 + Math.random() * 0.5).toFixed(2)}`, unit: "MPa" }, |
|
{ label: "信号强度", value: `-45 - ${Math.floor(Math.random() * 10)}`, unit: "dBm" } |
|
]; |
|
case "levelSensor": |
|
return [ |
|
{ label: "当前液位", value: `${Math.floor(3 + Math.random() * 5)}`, unit: "m" }, |
|
{ label: "信号强度", value: `-48 - ${Math.floor(Math.random() * 10)}`, unit: "dBm" } |
|
]; |
|
case "flowSensor": |
|
return [ |
|
{ label: "瞬时流量", value: `${(100 + Math.random() * 200).toFixed(1)}`, unit: "m³/h" }, |
|
{ label: "累计流量", value: `${Math.floor(10000 + Math.random() * 50000)}`, unit: "m³" }, |
|
{ label: "信号强度", value: `-50 - ${Math.floor(Math.random() * 10)}`, unit: "dBm" } |
|
]; |
|
default: |
|
return []; |
|
} |
|
}, |
|
getMeterParameters(index) { |
|
// TODO: 根据区域(area)参数返回不同区域的仪表数据 |
|
switch (index) { |
|
case 1: // 流量计 |
|
return [ |
|
{ label: "瞬时流量", value: `${(120 + Math.random() * 80).toFixed(1)}`, unit: "m³/h" }, |
|
{ label: "累计流量", value: `${Math.floor(50000 + Math.random() * 100000)}`, unit: "m³" }, |
|
{ label: "信号强度", value: `-42 - ${Math.floor(Math.random() * 12)}`, unit: "dBm" } |
|
]; |
|
case 2: // 压力表 |
|
return [ |
|
{ label: "当前压力", value: `${(0.35 + Math.random() * 0.35).toFixed(2)}`, unit: "MPa" }, |
|
{ label: "信号强度", value: `-45 - ${Math.floor(Math.random() * 10)}`, unit: "dBm" } |
|
]; |
|
case 3: // 电表 |
|
return [ |
|
{ label: "总有功电能", value: `${Math.floor(20000 + Math.random() * 80000)}`, unit: "kWh" }, |
|
{ label: "总功率", value: `${Math.floor(80 + Math.random() * 150)}`, unit: "kW" }, |
|
{ label: "A相电压", value: `${(218 + Math.random() * 5).toFixed(1)}`, unit: "V" }, |
|
{ label: "B相电压", value: `${(218 + Math.random() * 5).toFixed(1)}`, unit: "V" }, |
|
{ label: "C相电压", value: `${(218 + Math.random() * 5).toFixed(1)}`, unit: "V" } |
|
]; |
|
case 4: // 液位计 |
|
return [ |
|
{ label: "当前液位", value: `${Math.floor(4 + Math.random() * 4)}`, unit: "m" }, |
|
{ label: "水箱容积", value: `${Math.floor(80 + Math.random() * 40)}`, unit: "%" }, |
|
{ label: "信号强度", value: `-48 - ${Math.floor(Math.random() * 10)}`, unit: "dBm" } |
|
]; |
|
default: |
|
return []; |
|
} |
|
}, |
|
getRandomDate(startYear, endYear) { |
|
const start = new Date(startYear, 0, 1); |
|
const end = new Date(endYear, 11, 31); |
|
const date = new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); |
|
return date.toISOString().split('T')[0]; |
|
}, |
|
getRecentTime() { |
|
const now = new Date(); |
|
return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; |
|
}, |
|
getSubDeviceTypes() { |
|
const category = this.deviceCategories[this.activeCategory]; |
|
switch (category.value) { |
|
case "pump": |
|
return [ |
|
{ label: "高区水泵", value: "highZonePump" }, |
|
{ label: "中区水泵", value: "middleZonePump" }, |
|
{ label: "低区水泵", value: "lowZonePump" }, |
|
{ label: "消防水泵", value: "fireZonePump" } |
|
]; |
|
case "gateway": |
|
return [ |
|
{ label: "水泵网关", value: "pumpGateway" } |
|
]; |
|
case "meter": |
|
return [ |
|
{ label: "流量计", value: "pumpFlowMeter" }, |
|
{ label: "压力表", value: "pumpPressureMeter" }, |
|
{ label: "电表", value: "pumpEnergyMeter" }, |
|
{ label: "液位计", value: "pumpLevelMeter" } |
|
]; |
|
case "sensor": |
|
return [ |
|
{ label: "压力传感器", value: "pressureSensor" }, |
|
{ label: "液位传感器", value: "levelSensor" }, |
|
{ label: "流量传感器", value: "flowSensor" } |
|
]; |
|
default: |
|
return []; |
|
} |
|
}, |
|
getStatusText(status) { |
|
const statusMap = { |
|
running: "运行中", |
|
offline: "离线", |
|
fault: "故障" |
|
}; |
|
return statusMap[status] || status; |
|
}, |
|
getDeviceIconEmoji(type) { |
|
const iconMap = { |
|
pump: "🔧", |
|
gateway: "📡", |
|
meter: "⚡", |
|
sensor: "📊" |
|
}; |
|
return iconMap[type] || "📱"; |
|
}, |
|
getDeviceTypeName(type) { |
|
const typeMap = { |
|
pump: "水泵", |
|
gateway: "网关", |
|
meter: "仪表", |
|
sensor: "传感器" |
|
}; |
|
return typeMap[type] || type; |
|
}, |
|
getCategoryIcon(value) { |
|
const iconMap = { |
|
all: "📋", |
|
pump: "🔧", |
|
gateway: "📡", |
|
meter: "⚡", |
|
sensor: "📊" |
|
}; |
|
return iconMap[value] || "📱"; |
|
}, |
|
handleCategoryChange(index) { |
|
this.activeCategory = index; |
|
this.currentPage = 1; |
|
this.searchForm.deviceType = ""; |
|
}, |
|
handleSearch() { |
|
this.currentPage = 1; |
|
}, |
|
handleReset() { |
|
this.searchForm = { deviceName: "", deviceStatus: "", deviceType: "" }; |
|
this.activeCategory = 0; |
|
this.currentPage = 1; |
|
}, |
|
handleExport() { |
|
this.$message.success("设备列表导出成功"); |
|
}, |
|
handleSizeChange(size) { |
|
this.pageSize = size; |
|
this.currentPage = 1; |
|
}, |
|
handleCurrentChange(page) { |
|
this.currentPage = page; |
|
}, |
|
viewDeviceDetail(device) { |
|
this.selectedDevice = { ...device }; |
|
this.detailDialogVisible = true; |
|
}, |
|
closeDetailDialog() { |
|
this.detailDialogVisible = false; |
|
this.selectedDevice = null; |
|
}, |
|
saveControl() { |
|
this.$message.success("控制参数保存成功"); |
|
this.closeDetailDialog(); |
|
} |
|
} |
|
}; |
|
</script> |
|
|
|
<style lang="scss" scoped> |
|
.device-manage { |
|
padding: 0.16rem; |
|
background: rgba(11, 30, 48, 0.6); |
|
} |
|
|
|
.search-filter-area { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: flex-start; |
|
padding: 0.2rem; |
|
background: rgba(255, 255, 255, 0.05); |
|
border-radius: 0.08rem; |
|
margin-bottom: 0.16rem; |
|
flex-wrap: wrap; |
|
gap: 0.15rem; |
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
|
.search-group { |
|
display: flex; |
|
gap: 0.15rem; |
|
flex-wrap: wrap; |
|
flex: 1; |
|
min-width: 0; |
|
|
|
.search-item { |
|
display: flex; |
|
flex-direction: column; |
|
gap: 0.08rem; |
|
min-width: 2rem; |
|
|
|
label { |
|
font-size: 0.13rem; |
|
color: #ffffff; |
|
opacity: 0.7; |
|
} |
|
|
|
::v-deep .custom-input, |
|
::v-deep .custom-select { |
|
.el-input__wrapper, |
|
.el-select__wrapper { |
|
background: rgba(255, 255, 255, 0.1); |
|
border: 1px solid rgba(255, 255, 255, 0.2); |
|
box-shadow: none; |
|
|
|
&:hover { |
|
border-color: rgba(21, 225, 253, 0.5); |
|
} |
|
|
|
.el-input__inner { |
|
color: #ffffff; |
|
} |
|
} |
|
|
|
.search-icon { |
|
width: 0.16rem; |
|
height: 0.16rem; |
|
fill: rgba(255, 255, 255, 0.5); |
|
} |
|
} |
|
} |
|
} |
|
|
|
.action-group { |
|
display: flex; |
|
gap: 0.1rem; |
|
|
|
::v-deep .custom-btn { |
|
background: rgba(255, 255, 255, 0.1); |
|
border: 1px solid rgba(255, 255, 255, 0.2); |
|
color: #ffffff; |
|
|
|
&:hover { |
|
background: rgba(255, 255, 255, 0.2); |
|
} |
|
|
|
&.el-button--primary { |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
border: none; |
|
|
|
&:hover { |
|
background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%); |
|
} |
|
} |
|
|
|
&.el-button--success { |
|
background: linear-gradient(135deg, #91CC75 0%, #66BB6A 100%); |
|
border: none; |
|
|
|
.btn-icon { |
|
width: 0.14rem; |
|
height: 0.14rem; |
|
fill: #ffffff; |
|
margin-right: 0.04rem; |
|
} |
|
|
|
&:hover { |
|
background: linear-gradient(135deg, #7CB342 0%, #558B2F 100%); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
.device-stats { |
|
display: grid; |
|
grid-template-columns: repeat(4, 1fr); |
|
gap: 0.15rem; |
|
margin-bottom: 0.16rem; |
|
|
|
.stat-card { |
|
display: flex; |
|
align-items: center; |
|
padding: 0.2rem; |
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.03) 100%); |
|
border-radius: 0.08rem; |
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
transition: all 0.3s ease; |
|
|
|
&:hover { |
|
transform: translateY(-0.02rem); |
|
box-shadow: 0 0.08rem 0.2rem rgba(21, 225, 253, 0.15); |
|
} |
|
|
|
&.total { |
|
border-left: 0.04rem solid #5470C6; |
|
} |
|
|
|
&.running { |
|
border-left: 0.04rem solid #91CC75; |
|
} |
|
|
|
&.offline { |
|
border-left: 0.04rem solid #EE6666; |
|
} |
|
|
|
&.fault { |
|
border-left: 0.04rem solid #FAC858; |
|
} |
|
|
|
.stat-icon { |
|
width: 0.5rem; |
|
height: 0.5rem; |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
margin-right: 0.15rem; |
|
background: rgba(255, 255, 255, 0.1); |
|
|
|
svg { |
|
width: 0.26rem; |
|
height: 0.26rem; |
|
fill: #ffffff; |
|
} |
|
} |
|
|
|
.stat-info { |
|
flex: 1; |
|
|
|
.stat-value { |
|
font-size: 0.28rem; |
|
color: #15e1fd; |
|
font-weight: bold; |
|
margin-bottom: 0.03rem; |
|
} |
|
|
|
.stat-label { |
|
font-size: 0.13rem; |
|
color: #ffffff; |
|
opacity: 0.7; |
|
} |
|
} |
|
} |
|
} |
|
|
|
.category-tabs { |
|
display: flex; |
|
gap: 0.1rem; |
|
padding: 0.2rem; |
|
background: rgba(255, 255, 255, 0.05); |
|
border-radius: 0.08rem; |
|
margin-bottom: 0.16rem; |
|
overflow-x: auto; |
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
|
.category-tab { |
|
display: flex; |
|
align-items: center; |
|
gap: 0.08rem; |
|
padding: 0.1rem 0.2rem; |
|
font-size: 0.14rem; |
|
color: #ffffff; |
|
opacity: 0.7; |
|
cursor: pointer; |
|
border-radius: 0.06rem; |
|
transition: all 0.3s; |
|
white-space: nowrap; |
|
|
|
&:hover { |
|
opacity: 1; |
|
background: rgba(255, 255, 255, 0.1); |
|
} |
|
|
|
&.active { |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
opacity: 1; |
|
} |
|
|
|
.tab-icon { |
|
font-size: 0.16rem; |
|
} |
|
|
|
.tab-label { |
|
flex: 1; |
|
} |
|
|
|
.tab-count { |
|
font-size: 0.12rem; |
|
opacity: 0.8; |
|
} |
|
} |
|
} |
|
|
|
.device-list-container { |
|
background: rgba(255, 255, 255, 0.03); |
|
border-radius: 0.08rem; |
|
padding: 0.2rem; |
|
border: 1px solid rgba(255, 255, 255, 0.05); |
|
|
|
.list-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 0.2rem; |
|
|
|
.list-title { |
|
font-size: 0.16rem; |
|
color: #ffffff; |
|
font-weight: bold; |
|
padding-left: 0.12rem; |
|
border-left: 0.04rem solid #15e1fd; |
|
} |
|
|
|
.list-info { |
|
font-size: 0.14rem; |
|
color: #ffffff; |
|
opacity: 0.7; |
|
} |
|
} |
|
|
|
.device-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(3.5rem, 1fr)); |
|
gap: 0.15rem; |
|
margin-bottom: 0.2rem; |
|
|
|
.device-card { |
|
padding: 0.2rem; |
|
background: rgba(255, 255, 255, 0.05); |
|
border-radius: 0.08rem; |
|
cursor: pointer; |
|
transition: all 0.3s; |
|
border: 1px solid rgba(255, 255, 255, 0.05); |
|
|
|
&:hover { |
|
background: rgba(255, 255, 255, 0.08); |
|
transform: translateY(-0.03rem); |
|
box-shadow: 0 0.1rem 0.25rem rgba(0, 0, 0, 0.2); |
|
} |
|
|
|
&.running { |
|
border-left: 3px solid #91CC75; |
|
} |
|
|
|
&.offline { |
|
border-left: 3px solid #EE6666; |
|
} |
|
|
|
&.fault { |
|
border-left: 3px solid #FAC858; |
|
} |
|
|
|
.card-header { |
|
display: flex; |
|
align-items: center; |
|
margin-bottom: 0.15rem; |
|
|
|
.device-icon { |
|
width: 0.5rem; |
|
height: 0.5rem; |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
margin-right: 0.12rem; |
|
font-size: 0.24rem; |
|
color: #ffffff; |
|
} |
|
|
|
.device-header-info { |
|
flex: 1; |
|
|
|
.device-name { |
|
font-size: 0.15rem; |
|
color: #ffffff; |
|
font-weight: bold; |
|
margin-bottom: 0.04rem; |
|
} |
|
|
|
.device-code { |
|
font-size: 0.12rem; |
|
color: #ffffff; |
|
opacity: 0.6; |
|
} |
|
} |
|
|
|
.status-badge { |
|
padding: 0.04rem 0.12rem; |
|
border-radius: 0.04rem; |
|
font-size: 0.11rem; |
|
|
|
&.running { |
|
background: rgba(145, 204, 117, 0.2); |
|
color: #91CC75; |
|
border: 1px solid rgba(145, 204, 117, 0.3); |
|
} |
|
|
|
&.offline { |
|
background: rgba(238, 102, 102, 0.2); |
|
color: #EE6666; |
|
border: 1px solid rgba(238, 102, 102, 0.3); |
|
} |
|
|
|
&.fault { |
|
background: rgba(250, 200, 88, 0.2); |
|
color: #FAC858; |
|
border: 1px solid rgba(250, 200, 88, 0.3); |
|
} |
|
} |
|
} |
|
|
|
.card-body { |
|
margin-bottom: 0.15rem; |
|
|
|
.device-info-row { |
|
display: flex; |
|
justify-content: space-between; |
|
margin-bottom: 0.08rem; |
|
font-size: 0.13rem; |
|
|
|
.info-label { |
|
color: #ffffff; |
|
opacity: 0.6; |
|
} |
|
|
|
.info-value { |
|
color: #ffffff; |
|
opacity: 0.9; |
|
|
|
&.highlight { |
|
color: #15e1fd; |
|
font-weight: bold; |
|
} |
|
} |
|
} |
|
} |
|
|
|
.card-footer { |
|
.device-params { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 0.08rem; |
|
|
|
.param-item { |
|
padding: 0.06rem 0.1rem; |
|
background: rgba(255, 255, 255, 0.05); |
|
border-radius: 0.04rem; |
|
font-size: 0.11rem; |
|
color: #ffffff; |
|
opacity: 0.8; |
|
|
|
.param-label { |
|
opacity: 0.7; |
|
} |
|
|
|
.param-value { |
|
color: #15e1fd; |
|
font-weight: bold; |
|
margin: 0 0.04rem; |
|
} |
|
} |
|
|
|
.param-more { |
|
padding: 0.06rem 0.1rem; |
|
background: rgba(21, 225, 253, 0.1); |
|
border-radius: 0.04rem; |
|
font-size: 0.11rem; |
|
color: #15e1fd; |
|
opacity: 0.8; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
.pagination-container { |
|
display: flex; |
|
justify-content: center; |
|
padding-top: 0.2rem; |
|
|
|
::v-deep .custom-pagination { |
|
.el-pagination__total, |
|
.el-pagination__jump, |
|
.el-pager li { |
|
color: #ffffff; |
|
} |
|
|
|
.el-pager li { |
|
background: rgba(255, 255, 255, 0.05); |
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
|
&:hover { |
|
background: rgba(255, 255, 255, 0.1); |
|
} |
|
|
|
&.is-active { |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
border-color: transparent; |
|
} |
|
} |
|
|
|
.btn-prev, |
|
.btn-next { |
|
background: rgba(255, 255, 255, 0.05); |
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
color: #ffffff; |
|
|
|
&:hover { |
|
background: rgba(255, 255, 255, 0.1); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
::v-deep .custom-dialog { |
|
.el-dialog { |
|
background: linear-gradient(135deg, rgba(11, 30, 48, 0.98) 0%, rgba(20, 45, 65, 0.98) 100%); |
|
border: 1px solid rgba(21, 225, 253, 0.3); |
|
box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.4); |
|
} |
|
|
|
.el-dialog__header { |
|
background: rgba(255, 255, 255, 0.05); |
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
|
} |
|
|
|
.el-dialog__title { |
|
color: #ffffff; |
|
} |
|
|
|
.el-dialog__headerbtn .el-dialog__close { |
|
color: #ffffff; |
|
} |
|
|
|
.detail-content { |
|
.detail-section { |
|
margin-bottom: 0.3rem; |
|
|
|
.section-title { |
|
font-size: 0.15rem; |
|
color: #15e1fd; |
|
font-weight: bold; |
|
margin-bottom: 0.15rem; |
|
padding-left: 0.12rem; |
|
border-left: 0.04rem solid #15e1fd; |
|
} |
|
|
|
.info-grid { |
|
display: grid; |
|
grid-template-columns: repeat(2, 1fr); |
|
gap: 0.15rem; |
|
|
|
.info-item { |
|
display: flex; |
|
justify-content: space-between; |
|
padding: 0.12rem; |
|
background: rgba(255, 255, 255, 0.03); |
|
border-radius: 0.06rem; |
|
|
|
&.full-width { |
|
grid-column: 1 / -1; |
|
} |
|
|
|
.item-label { |
|
font-size: 0.14rem; |
|
color: #ffffff; |
|
opacity: 0.7; |
|
} |
|
|
|
.item-value { |
|
font-size: 0.14rem; |
|
color: #ffffff; |
|
|
|
&.status-badge { |
|
padding: 0.04rem 0.12rem; |
|
border-radius: 0.04rem; |
|
font-size: 0.12rem; |
|
|
|
&.running { |
|
background: rgba(145, 204, 117, 0.2); |
|
color: #91CC75; |
|
} |
|
|
|
&.offline { |
|
background: rgba(238, 102, 102, 0.2); |
|
color: #EE6666; |
|
} |
|
|
|
&.fault { |
|
background: rgba(250, 200, 88, 0.2); |
|
color: #FAC858; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
.params-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(1.5rem, 1fr)); |
|
gap: 0.15rem; |
|
|
|
.param-card { |
|
padding: 0.2rem; |
|
background: rgba(255, 255, 255, 0.05); |
|
border-radius: 0.06rem; |
|
text-align: center; |
|
transition: all 0.3s ease; |
|
|
|
&:hover { |
|
background: rgba(255, 255, 255, 0.08); |
|
transform: translateY(-0.02rem); |
|
} |
|
|
|
.param-label { |
|
font-size: 0.12rem; |
|
color: #ffffff; |
|
opacity: 0.6; |
|
margin-bottom: 0.08rem; |
|
} |
|
|
|
.param-value { |
|
font-size: 0.2rem; |
|
color: #15e1fd; |
|
font-weight: bold; |
|
margin-bottom: 0.04rem; |
|
} |
|
|
|
.param-unit { |
|
font-size: 0.11rem; |
|
color: #ffffff; |
|
opacity: 0.5; |
|
} |
|
} |
|
} |
|
|
|
.status-info { |
|
.status-item { |
|
display: flex; |
|
justify-content: space-between; |
|
padding: 0.15rem; |
|
background: rgba(255, 255, 255, 0.03); |
|
border-radius: 0.06rem; |
|
margin-bottom: 0.1rem; |
|
|
|
.status-label { |
|
font-size: 0.14rem; |
|
color: #ffffff; |
|
opacity: 0.7; |
|
} |
|
|
|
.status-text { |
|
font-size: 0.14rem; |
|
color: #ffffff; |
|
|
|
&.running { |
|
color: #91CC75; |
|
} |
|
|
|
&.offline { |
|
color: #EE6666; |
|
} |
|
|
|
&.fault { |
|
color: #FAC858; |
|
} |
|
} |
|
} |
|
} |
|
|
|
.control-grid { |
|
.control-item { |
|
display: flex; |
|
align-items: center; |
|
gap: 0.15rem; |
|
padding: 0.15rem; |
|
background: rgba(255, 255, 255, 0.03); |
|
border-radius: 0.06rem; |
|
margin-bottom: 0.1rem; |
|
|
|
.control-label { |
|
min-width: 1rem; |
|
font-size: 0.14rem; |
|
color: #ffffff; |
|
opacity: 0.7; |
|
} |
|
|
|
.el-slider { |
|
flex: 1; |
|
} |
|
|
|
.control-value { |
|
min-width: 0.8rem; |
|
text-align: right; |
|
font-size: 0.14rem; |
|
color: #15e1fd; |
|
font-weight: bold; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
.dialog-footer { |
|
display: flex; |
|
justify-content: flex-end; |
|
gap: 0.1rem; |
|
} |
|
} |
|
|
|
::v-deep .el-slider { |
|
.el-slider__runway { |
|
background: rgba(255, 255, 255, 0.2); |
|
} |
|
|
|
.el-slider__bar { |
|
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); |
|
} |
|
|
|
.el-slider__button { |
|
border-color: #667eea; |
|
} |
|
} |
|
|
|
@media (max-width: 1485px) { |
|
.device-stats { |
|
grid-template-columns: repeat(2, 1fr); |
|
} |
|
|
|
.device-grid { |
|
grid-template-columns: repeat(auto-fill, minmax(3rem, 1fr)); |
|
} |
|
} |
|
|
|
@media (max-width: 768px) { |
|
.search-filter-area { |
|
flex-direction: column; |
|
|
|
.search-group { |
|
width: 100%; |
|
flex-direction: column; |
|
|
|
.search-item { |
|
width: 100%; |
|
|
|
::v-deep .custom-input, |
|
::v-deep .custom-select { |
|
width: 100%; |
|
} |
|
} |
|
} |
|
|
|
.action-group { |
|
width: 100%; |
|
justify-content: flex-end; |
|
} |
|
} |
|
|
|
.device-stats { |
|
grid-template-columns: repeat(2, 1fr); |
|
} |
|
|
|
.category-tabs { |
|
flex-wrap: wrap; |
|
} |
|
|
|
.detail-content { |
|
.info-grid { |
|
grid-template-columns: 1fr; |
|
} |
|
} |
|
} |
|
</style>
|
|
|