|
|
@@ -1,668 +0,0 @@
|
|
|
-<template>
|
|
|
- <div class="threshold-management">
|
|
|
- <div class="main-content">
|
|
|
- <div class="point-list-panel">
|
|
|
- <div class="panel-title">
|
|
|
- <span>设备列表</span>
|
|
|
- <el-button :icon="Refresh" text @click="loadDeviceList">刷新</el-button>
|
|
|
- </div>
|
|
|
- <el-input
|
|
|
- v-model="searchKeyword"
|
|
|
- placeholder="搜索设备编号/名称"
|
|
|
- clearable
|
|
|
- :prefix-icon="Search"
|
|
|
- size="small"
|
|
|
- @input="handleSearch"
|
|
|
- />
|
|
|
-
|
|
|
- <div v-loading="listLoading" class="point-list">
|
|
|
- <div
|
|
|
- v-for="point in deviceList"
|
|
|
- :key="point.equipmentId"
|
|
|
- class="point-item"
|
|
|
- :class="{ active: selectedPoint?.equipmentId === point.equipmentId }"
|
|
|
- @click="selectPoint(point)"
|
|
|
- >
|
|
|
- <div class="point-header">
|
|
|
- <span class="point-name">{{ point.equipmentName || point.equipmentCode || '未命名设备' }}</span>
|
|
|
- <el-tag :type="getAlarmTagType(point)" size="small">
|
|
|
- {{ getAlarmText(point) }}
|
|
|
- </el-tag>
|
|
|
- </div>
|
|
|
- <div class="point-location">
|
|
|
- <el-icon><Location /></el-icon>
|
|
|
- <span>{{ point.equipmentLocation || '暂无位置' }}</span>
|
|
|
- </div>
|
|
|
- <div class="point-meta">
|
|
|
- <span>编号:{{ point.equipmentCode || '-' }}</span>
|
|
|
- <span>类型:{{ point.equipmentTypeName || '-' }}</span>
|
|
|
- </div>
|
|
|
- <div class="point-status-row">
|
|
|
- <el-tag size="small" effect="plain" :type="getOnlineTagType(point.equipmentStatus?.onlineStatus)">
|
|
|
- {{ getOnlineText(point.equipmentStatus?.onlineStatus) }}
|
|
|
- </el-tag>
|
|
|
- <el-tag size="small" effect="plain" :type="getUseStatusTagType(point.equipmentStatus?.currentStatus)">
|
|
|
- {{ getUseStatusText(point.equipmentStatus?.currentStatus) }}
|
|
|
- </el-tag>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <el-empty
|
|
|
- v-if="!listLoading && !deviceList.length"
|
|
|
- description="暂无井盖监测设备"
|
|
|
- :image-size="120"
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="pagination-area">
|
|
|
- <el-pagination
|
|
|
- small
|
|
|
- background
|
|
|
- layout="total, prev, pager, next"
|
|
|
- :total="total"
|
|
|
- :page-size="pageSize"
|
|
|
- v-model:current-page="currentPage"
|
|
|
- @current-change="loadDeviceList"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div v-if="selectedPoint" v-loading="detailLoading" class="config-panel">
|
|
|
- <div class="info-card">
|
|
|
- <div class="card-title">
|
|
|
- <span><el-icon><Monitor /></el-icon> 设备基本信息</span>
|
|
|
- </div>
|
|
|
- <el-descriptions :column="2" border>
|
|
|
- <el-descriptions-item label="设备编号">{{ selectedDetail?.equipmentCode || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="设备名称">{{ selectedDetail?.equipmentName || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="设备类型">{{ selectedDetail?.equipmentTypeName || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="设备型号">{{ selectedDetail?.equipmentModel || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="生产厂商">{{ selectedDetail?.manufacturer || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="安装时间">{{ formatDateTime(selectedDetail?.installTime) }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="设备位置">{{ selectedDetail?.equipmentLocation || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="维护人员">{{ selectedDetail?.maintainer || '-' }}</el-descriptions-item>
|
|
|
- </el-descriptions>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="info-card">
|
|
|
- <div class="card-title">
|
|
|
- <span><el-icon><Warning /></el-icon> 最新监测状态</span>
|
|
|
- </div>
|
|
|
- <div class="status-grid">
|
|
|
- <div class="status-item">
|
|
|
- <div class="status-label">在线状态</div>
|
|
|
- <div class="status-value">{{ getOnlineText(selectedDetail?.equipmentStatus?.onlineStatus) }}</div>
|
|
|
- </div>
|
|
|
- <div class="status-item">
|
|
|
- <div class="status-label">报警状态</div>
|
|
|
- <div class="status-value">{{ getAlarmText(selectedDetail) }}</div>
|
|
|
- </div>
|
|
|
- <div class="status-item">
|
|
|
- <div class="status-label">倾斜角度</div>
|
|
|
- <div class="status-value">{{ appendUnit(selectedDetail?.manholeData?.tiltAngle, '°') }}</div>
|
|
|
- </div>
|
|
|
- <div class="status-item">
|
|
|
- <div class="status-label">电池电量</div>
|
|
|
- <div class="status-value">{{ appendUnit(selectedDetail?.manholeData?.batteryLevel, '%') }}</div>
|
|
|
- </div>
|
|
|
- <div class="status-item">
|
|
|
- <div class="status-label">信号强度</div>
|
|
|
- <div class="status-value">{{ appendUnit(selectedDetail?.manholeData?.signalStrength, '%') }}</div>
|
|
|
- </div>
|
|
|
- <div class="status-item">
|
|
|
- <div class="status-label">水浸状态</div>
|
|
|
- <div class="status-value">{{ getBinaryAlarmText(selectedDetail?.manholeData?.waterInfiltrationAlarmStatus) }}</div>
|
|
|
- </div>
|
|
|
- <div class="status-item">
|
|
|
- <div class="status-label">水位状态</div>
|
|
|
- <div class="status-value">{{ getBinaryAlarmText(selectedDetail?.manholeData?.waterLevelAlarmStatus) }}</div>
|
|
|
- </div>
|
|
|
- <div class="status-item">
|
|
|
- <div class="status-label">激活状态</div>
|
|
|
- <div class="status-value">{{ selectedDetail?.manholeData?.activationStatus || '-' }}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="threshold-card">
|
|
|
- <div class="card-title">
|
|
|
- <span><el-icon><Setting /></el-icon> 报警阈值设置</span>
|
|
|
- <div class="card-actions">
|
|
|
- <el-button :icon="Refresh" size="small" @click="resetThresholds">重置</el-button>
|
|
|
- <el-button :loading="saveLoading" size="small" type="primary" @click="saveThresholds">保存设置</el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="threshold-tip">
|
|
|
- 当前版本先对接后端已支持的 3 类阈值:倾斜、水位、温度。
|
|
|
- </div>
|
|
|
-
|
|
|
- <el-tabs v-model="activeTab" class="threshold-tabs">
|
|
|
- <el-tab-pane
|
|
|
- v-for="item in thresholdConfigList"
|
|
|
- :key="item.key"
|
|
|
- :label="item.label"
|
|
|
- :name="item.key"
|
|
|
- >
|
|
|
- <div class="threshold-item">
|
|
|
- <div class="item-label">{{ item.desc }}</div>
|
|
|
- <div class="item-control">
|
|
|
- <span class="field-label">最小值</span>
|
|
|
- <el-input-number
|
|
|
- v-model="thresholds[item.key].minValue"
|
|
|
- :controls="false"
|
|
|
- :precision="item.precision"
|
|
|
- :step="item.step"
|
|
|
- class="threshold-input"
|
|
|
- placeholder="未设置"
|
|
|
- />
|
|
|
- <span class="unit">{{ item.unit }}</span>
|
|
|
-
|
|
|
- <span class="field-label">最大值</span>
|
|
|
- <el-input-number
|
|
|
- v-model="thresholds[item.key].maxValue"
|
|
|
- :controls="false"
|
|
|
- :precision="item.precision"
|
|
|
- :step="item.step"
|
|
|
- class="threshold-input"
|
|
|
- placeholder="未设置"
|
|
|
- />
|
|
|
- <span class="unit">{{ item.unit }}</span>
|
|
|
- </div>
|
|
|
- <el-input
|
|
|
- v-model="thresholds[item.key].remark"
|
|
|
- maxlength="100"
|
|
|
- placeholder="请输入备注"
|
|
|
- />
|
|
|
- <div class="saved-hint">
|
|
|
- 当前阈值配置将保存到后端预警规则
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </el-tab-pane>
|
|
|
- </el-tabs>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div v-else class="empty-panel">
|
|
|
- <el-empty description="请从左侧选择设备查看阈值配置" :image-size="140" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup>
|
|
|
-import {onBeforeUnmount, onMounted, ref} from 'vue'
|
|
|
-import {ElMessage} from 'element-plus'
|
|
|
-import {Location, Monitor, Refresh, Search, Setting, Warning} from '@element-plus/icons-vue'
|
|
|
-import {
|
|
|
- getManholeAlarmDeviceDetail,
|
|
|
- getManholeDevicePage,
|
|
|
- saveManholeAlarmThreshold
|
|
|
-} from '@/api/pipeNetwork/basic'
|
|
|
-
|
|
|
-const thresholdConfigList = [
|
|
|
- {
|
|
|
- key: 'tilt',
|
|
|
- label: '倾斜监测',
|
|
|
- warningType: '倾斜预警',
|
|
|
- warningCode: 'WARN-TILT',
|
|
|
- unit: '°',
|
|
|
- step: 0.1,
|
|
|
- precision: 1,
|
|
|
- desc: '当倾斜角度小于等于最小值或大于等于最大值时触发报警'
|
|
|
- },
|
|
|
- {
|
|
|
- key: 'waterLevel',
|
|
|
- label: '水位监测',
|
|
|
- warningType: '水位预警',
|
|
|
- warningCode: 'WARN-WATER-LEVEL',
|
|
|
- unit: 'cm',
|
|
|
- step: 0.1,
|
|
|
- precision: 1,
|
|
|
- desc: '用于井盖水位报警阈值配置'
|
|
|
- },
|
|
|
- {
|
|
|
- key: 'temperature',
|
|
|
- label: '温度监测',
|
|
|
- warningType: '温度预警',
|
|
|
- warningCode: 'WARN-TEMPERATURE',
|
|
|
- unit: '℃',
|
|
|
- step: 0.1,
|
|
|
- precision: 1,
|
|
|
- desc: '用于设备温度报警阈值配置'
|
|
|
- }
|
|
|
-]
|
|
|
-
|
|
|
-const searchKeyword = ref('')
|
|
|
-const activeTab = ref('tilt')
|
|
|
-const listLoading = ref(false)
|
|
|
-const detailLoading = ref(false)
|
|
|
-const saveLoading = ref(false)
|
|
|
-const deviceList = ref([])
|
|
|
-const total = ref(0)
|
|
|
-const currentPage = ref(1)
|
|
|
-const pageSize = 8
|
|
|
-const selectedPoint = ref(null)
|
|
|
-const selectedDetail = ref(null)
|
|
|
-const thresholds = ref(createEmptyThresholds())
|
|
|
-
|
|
|
-// 搜索防抖
|
|
|
-let searchTimer = null
|
|
|
-function handleSearch() {
|
|
|
- clearTimeout(searchTimer)
|
|
|
- searchTimer = setTimeout(() => {
|
|
|
- currentPage.value = 1
|
|
|
- loadDeviceList()
|
|
|
- }, 300)
|
|
|
-}
|
|
|
-
|
|
|
-function createEmptyThresholds() {
|
|
|
- return thresholdConfigList.reduce((acc, item) => {
|
|
|
- acc[item.key] = {
|
|
|
- id: '',
|
|
|
- minValue: null,
|
|
|
- maxValue: null,
|
|
|
- remark: ''
|
|
|
- }
|
|
|
- return acc
|
|
|
- }, {})
|
|
|
-}
|
|
|
-
|
|
|
-function normalizeThresholdList(list) {
|
|
|
- const safeList = Array.isArray(list) ? list : []
|
|
|
- const result = createEmptyThresholds()
|
|
|
- thresholdConfigList.forEach((item) => {
|
|
|
- const matched = safeList.find(threshold => threshold.warningCode === item.warningCode)
|
|
|
- if (matched) {
|
|
|
- result[item.key] = {
|
|
|
- id: matched.id || '',
|
|
|
- minValue: matched.minValue ?? null,
|
|
|
- maxValue: matched.maxValue ?? null,
|
|
|
- remark: matched.remark || ''
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
- return result
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-function getOnlineText(status) {
|
|
|
- return Number(status) === 1 ? '在线' : '离线'
|
|
|
-}
|
|
|
-
|
|
|
-function getOnlineTagType(status) {
|
|
|
- return Number(status) === 1 ? 'success' : 'info'
|
|
|
-}
|
|
|
-
|
|
|
-function getAlarmText(point) {
|
|
|
- return Number(point?.equipmentStatus?.alarmStatus) === 1 ? '报警' : '正常'
|
|
|
-}
|
|
|
-
|
|
|
-function getAlarmTagType(point) {
|
|
|
- return Number(point?.equipmentStatus?.alarmStatus) === 1 ? 'danger' : 'success'
|
|
|
-}
|
|
|
-
|
|
|
-function getUseStatusText(status) {
|
|
|
- const map = {
|
|
|
- 1: '在用',
|
|
|
- 2: '闲置',
|
|
|
- 3: '维修',
|
|
|
- 4: '报废',
|
|
|
- 5: '待入库'
|
|
|
- }
|
|
|
- return map[Number(status)] || '未知'
|
|
|
-}
|
|
|
-
|
|
|
-function getUseStatusTagType(status) {
|
|
|
- const map = {
|
|
|
- 1: 'success',
|
|
|
- 2: 'info',
|
|
|
- 3: 'warning',
|
|
|
- 4: 'danger',
|
|
|
- 5: ''
|
|
|
- }
|
|
|
- return map[Number(status)] || 'info'
|
|
|
-}
|
|
|
-
|
|
|
-function getBinaryAlarmText(status) {
|
|
|
- return `${status}` === '1' ? '报警' : `${status}` === '0' ? '正常' : '-'
|
|
|
-}
|
|
|
-
|
|
|
-function appendUnit(value, unit) {
|
|
|
- if (value === undefined || value === null || value === '') return '-'
|
|
|
- return `${value}${unit}`
|
|
|
-}
|
|
|
-
|
|
|
-function formatDateTime(value) {
|
|
|
- return value || '-'
|
|
|
-}
|
|
|
-
|
|
|
-async function loadDeviceList() {
|
|
|
- listLoading.value = true
|
|
|
- try {
|
|
|
- const res = await getManholeDevicePage({
|
|
|
- pageNum: currentPage.value,
|
|
|
- pageSize,
|
|
|
- deviceName: searchKeyword.value.trim() || null,
|
|
|
- isQueryManholeData: true
|
|
|
- })
|
|
|
- const list = res.data?.records || []
|
|
|
- deviceList.value = list
|
|
|
- total.value = res.data?.total || list.length
|
|
|
-
|
|
|
- if (!list.length) {
|
|
|
- selectedPoint.value = null
|
|
|
- selectedDetail.value = null
|
|
|
- thresholds.value = createEmptyThresholds()
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- const currentId = selectedPoint.value?.equipmentId
|
|
|
- const currentPoint = list.find(item => item.equipmentId === currentId) || list[0]
|
|
|
- await selectPoint(currentPoint)
|
|
|
- } catch (error) {
|
|
|
- deviceList.value = []
|
|
|
- selectedPoint.value = null
|
|
|
- selectedDetail.value = null
|
|
|
- } finally {
|
|
|
- listLoading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-async function selectPoint(point) {
|
|
|
- if (!point?.equipmentId) return
|
|
|
- selectedPoint.value = point
|
|
|
- await loadDeviceDetail(point.equipmentId)
|
|
|
-}
|
|
|
-
|
|
|
-async function loadDeviceDetail(id) {
|
|
|
- detailLoading.value = true
|
|
|
- try {
|
|
|
- const res = await getManholeAlarmDeviceDetail(id)
|
|
|
- selectedDetail.value = res?.data || null
|
|
|
- thresholds.value = normalizeThresholdList(selectedDetail.value?.warningThresholdList)
|
|
|
- } catch (error) {
|
|
|
- selectedDetail.value = null
|
|
|
- thresholds.value = createEmptyThresholds()
|
|
|
- } finally {
|
|
|
- detailLoading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-async function resetThresholds() {
|
|
|
- if (!selectedPoint.value?.equipmentId) return
|
|
|
- await loadDeviceDetail(selectedPoint.value.equipmentId)
|
|
|
- ElMessage.success('已恢复为服务端最新阈值')
|
|
|
-}
|
|
|
-
|
|
|
-function hasThresholdValue(item) {
|
|
|
- return (
|
|
|
- (item.minValue !== null && item.minValue !== undefined) ||
|
|
|
- (item.maxValue !== null && item.maxValue !== undefined)
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-async function saveThresholds() {
|
|
|
- if (!selectedDetail.value?.equipmentCode) {
|
|
|
- ElMessage.warning('未获取到设备编码,无法保存')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- const invalidConfigs = thresholdConfigList.filter((config) => {
|
|
|
- const formItem = thresholds.value[config.key]
|
|
|
- return formItem.id && !hasThresholdValue(formItem)
|
|
|
- })
|
|
|
-
|
|
|
- if (invalidConfigs.length) {
|
|
|
- ElMessage.warning(`${invalidConfigs[0].label}请至少保留一个阈值`)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- const payloads = thresholdConfigList
|
|
|
- .map((config) => {
|
|
|
- const formItem = thresholds.value[config.key]
|
|
|
- if (!hasThresholdValue(formItem) && !formItem.id) return null
|
|
|
- return {
|
|
|
- id: formItem.id || undefined,
|
|
|
- deviceCode: selectedDetail.value.equipmentCode,
|
|
|
- warningType: config.warningType,
|
|
|
- warningCode: config.warningCode,
|
|
|
- minValue: formItem.minValue,
|
|
|
- maxValue: formItem.maxValue,
|
|
|
- remark: formItem.remark || ''
|
|
|
- }
|
|
|
- })
|
|
|
- .filter(Boolean)
|
|
|
-
|
|
|
- if (!payloads.length) {
|
|
|
- ElMessage.warning('请先填写至少一项阈值')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- saveLoading.value = true
|
|
|
- try {
|
|
|
- await Promise.all(payloads.map(item => saveManholeAlarmThreshold(item)))
|
|
|
- ElMessage.success('阈值保存成功')
|
|
|
- await loadDeviceDetail(selectedPoint.value.equipmentId)
|
|
|
- } finally {
|
|
|
- saveLoading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-onMounted(() => {
|
|
|
- loadDeviceList()
|
|
|
-})
|
|
|
-
|
|
|
-onBeforeUnmount(() => {
|
|
|
- clearTimeout(searchTimer)
|
|
|
-})
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped>
|
|
|
-.threshold-management {
|
|
|
- padding: 20px;
|
|
|
- background: #f0f2f6;
|
|
|
- min-height: 100vh;
|
|
|
-}
|
|
|
-
|
|
|
-.main-content {
|
|
|
- display: flex;
|
|
|
- gap: 20px;
|
|
|
-}
|
|
|
-
|
|
|
-.point-list-panel {
|
|
|
- width: 340px;
|
|
|
- background: #fff;
|
|
|
- border-radius: 16px;
|
|
|
- padding: 16px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 12px;
|
|
|
-}
|
|
|
-
|
|
|
-.panel-title {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- font-weight: 600;
|
|
|
-}
|
|
|
-
|
|
|
-.point-list {
|
|
|
- max-height: calc(100vh - 180px);
|
|
|
- overflow-y: auto;
|
|
|
-}
|
|
|
-
|
|
|
-.point-item {
|
|
|
- border: 1px solid #e5e7eb;
|
|
|
- border-radius: 12px;
|
|
|
- padding: 14px;
|
|
|
- margin-bottom: 12px;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.2s ease;
|
|
|
-}
|
|
|
-
|
|
|
-.point-item:hover {
|
|
|
- background: #f8fbff;
|
|
|
- border-color: #bfdbfe;
|
|
|
-}
|
|
|
-
|
|
|
-.point-item.active {
|
|
|
- background: #ecf5ff;
|
|
|
- border-color: #409eff;
|
|
|
-}
|
|
|
-
|
|
|
-.point-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- gap: 8px;
|
|
|
- margin-bottom: 8px;
|
|
|
-}
|
|
|
-
|
|
|
-.point-name {
|
|
|
- font-weight: 600;
|
|
|
- color: #303133;
|
|
|
-}
|
|
|
-
|
|
|
-.point-location {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 4px;
|
|
|
- color: #606266;
|
|
|
- font-size: 12px;
|
|
|
- margin-bottom: 8px;
|
|
|
-}
|
|
|
-
|
|
|
-.point-meta {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 4px;
|
|
|
- font-size: 12px;
|
|
|
- color: #409eff;
|
|
|
- margin-bottom: 8px;
|
|
|
-}
|
|
|
-
|
|
|
-.point-status-row {
|
|
|
- display: flex;
|
|
|
- gap: 8px;
|
|
|
-}
|
|
|
-
|
|
|
-.config-panel {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 20px;
|
|
|
-}
|
|
|
-
|
|
|
-.info-card,
|
|
|
-.threshold-card {
|
|
|
- background: #fff;
|
|
|
- border-radius: 16px;
|
|
|
- padding: 18px 20px;
|
|
|
- box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
|
|
|
-}
|
|
|
-
|
|
|
-.card-title {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 16px;
|
|
|
- padding-left: 12px;
|
|
|
- border-left: 4px solid #409eff;
|
|
|
- font-size: 16px;
|
|
|
- font-weight: 600;
|
|
|
-}
|
|
|
-
|
|
|
-.card-actions {
|
|
|
- display: flex;
|
|
|
- gap: 8px;
|
|
|
-}
|
|
|
-
|
|
|
-.status-grid {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
|
- gap: 12px;
|
|
|
-}
|
|
|
-
|
|
|
-.status-item {
|
|
|
- background: #f8fafc;
|
|
|
- border: 1px solid #edf2f7;
|
|
|
- border-radius: 12px;
|
|
|
- padding: 14px;
|
|
|
-}
|
|
|
-
|
|
|
-.status-label {
|
|
|
- color: #909399;
|
|
|
- font-size: 13px;
|
|
|
- margin-bottom: 8px;
|
|
|
-}
|
|
|
-
|
|
|
-.status-value {
|
|
|
- color: #303133;
|
|
|
- font-size: 16px;
|
|
|
- font-weight: 600;
|
|
|
-}
|
|
|
-
|
|
|
-.threshold-tip {
|
|
|
- margin-bottom: 12px;
|
|
|
- padding: 10px 12px;
|
|
|
- border-radius: 8px;
|
|
|
- background: #f4f9ff;
|
|
|
- color: #606266;
|
|
|
- font-size: 13px;
|
|
|
-}
|
|
|
-
|
|
|
-.threshold-item {
|
|
|
- padding: 8px 4px 4px;
|
|
|
-}
|
|
|
-
|
|
|
-.item-label {
|
|
|
- margin-bottom: 16px;
|
|
|
- color: #606266;
|
|
|
- font-size: 14px;
|
|
|
-}
|
|
|
-
|
|
|
-.item-control {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- flex-wrap: wrap;
|
|
|
- gap: 8px;
|
|
|
- margin-bottom: 16px;
|
|
|
-}
|
|
|
-
|
|
|
-.field-label {
|
|
|
- color: #303133;
|
|
|
- font-weight: 500;
|
|
|
-}
|
|
|
-
|
|
|
-.threshold-input {
|
|
|
- width: 160px;
|
|
|
-}
|
|
|
-
|
|
|
-.unit {
|
|
|
- color: #909399;
|
|
|
- margin-right: 16px;
|
|
|
-}
|
|
|
-
|
|
|
-.saved-hint {
|
|
|
- margin-top: 10px;
|
|
|
- color: #909399;
|
|
|
- font-size: 12px;
|
|
|
-}
|
|
|
-
|
|
|
-.empty-panel {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- background: #fff;
|
|
|
- border-radius: 16px;
|
|
|
-}
|
|
|
-
|
|
|
-.pagination-area {
|
|
|
- padding: 8px 0 0;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-</style>
|