|
@@ -0,0 +1,520 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="water-page threshold-page">
|
|
|
|
|
+ <div class="page-heading">
|
|
|
|
|
+ <div class="page-title">
|
|
|
|
|
+ <span class="page-title-icon"><el-icon><Warning /></el-icon></span>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <h2>阈值管理</h2>
|
|
|
|
|
+ <span class="muted">供水监测设备阈值、分时规则与生效范围统一配置</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="heading-actions">
|
|
|
|
|
+ <el-button type="primary" :icon="Plus" @click="openCreate">新增阈值</el-button>
|
|
|
|
|
+ <el-button :icon="Refresh" :loading="loading" @click="loadRules">刷新</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="metric-grid">
|
|
|
|
|
+ <div class="metric">
|
|
|
|
|
+ <div class="metric-icon"><el-icon><Connection /></el-icon></div>
|
|
|
|
|
+ <div class="metric-body">
|
|
|
|
|
+ <span>供水监测设备</span>
|
|
|
|
|
+ <div class="metric-value"><strong>{{ devices.length }}</strong><small>台</small></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="metric">
|
|
|
|
|
+ <div class="metric-icon"><el-icon><Bell /></el-icon></div>
|
|
|
|
|
+ <div class="metric-body">
|
|
|
|
|
+ <span>阈值规则</span>
|
|
|
|
|
+ <div class="metric-value"><strong>{{ page.total }}</strong><small>条</small></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="metric">
|
|
|
|
|
+ <div class="metric-icon"><el-icon><CircleCheck /></el-icon></div>
|
|
|
|
|
+ <div class="metric-body">
|
|
|
|
|
+ <span>当前页全天规则</span>
|
|
|
|
|
+ <div class="metric-value"><strong>{{ allDayCount }}</strong><small>条</small></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="metric">
|
|
|
|
|
+ <div class="metric-icon"><el-icon><Clock /></el-icon></div>
|
|
|
|
|
+ <div class="metric-body">
|
|
|
|
|
+ <span>当前页分时规则</span>
|
|
|
|
|
+ <div class="metric-value"><strong>{{ timedCount }}</strong><small>条</small></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-card shadow="never" class="water-panel filter-card">
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="card-title">
|
|
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
|
|
+ <span>阈值筛选</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <el-form :inline="true" :model="query" class="filter-form">
|
|
|
|
|
+ <el-form-item label="设备编码">
|
|
|
|
|
+ <el-input v-model="query.deviceCode" clearable placeholder="请输入供水设备编码" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="预警分类">
|
|
|
|
|
+ <el-select v-model="query.warningType" clearable filterable placeholder="展示全部" class="warning-select">
|
|
|
|
|
+ <el-option v-for="item in warningTypes" :key="item" :label="item" :value="item" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
|
|
|
|
|
+ <el-button @click="resetQuery">展示全部</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ v-if="loadError"
|
|
|
|
|
+ class="load-error"
|
|
|
|
|
+ type="error"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ title="阈值加载失败"
|
|
|
|
|
+ :description="loadError"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button size="small" type="primary" :loading="loading" @click="loadRules">重试</el-button>
|
|
|
|
|
+ </el-alert>
|
|
|
|
|
+
|
|
|
|
|
+ <el-card shadow="never" class="water-panel table-card">
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="card-title">
|
|
|
|
|
+ <el-icon><DataLine /></el-icon>
|
|
|
|
|
+ <span>阈值配置清单</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <el-table :data="page.records" stripe border v-loading="loading">
|
|
|
|
|
+ <el-table-column type="index" label="#" width="55" />
|
|
|
|
|
+ <el-table-column prop="deviceCode" label="供水设备编码" min-width="190" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="warningType" label="预警分类" min-width="120">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag effect="light">{{ row.warningType }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="warningCode" label="预警编码" min-width="150" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="阈值范围" min-width="160">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="threshold-range">{{ thresholdText(row) }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="生效时段" min-width="140">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag :type="isTimed(row) ? 'warning' : 'success'" effect="plain">
|
|
|
|
|
+ {{ periodText(row) }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="updateTime" label="更新时间" min-width="170" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="操作" width="150" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button link type="primary" :icon="Edit" @click="openEdit(row)">编辑</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ :icon="Delete"
|
|
|
|
|
+ :loading="deletingId === row.id"
|
|
|
|
|
+ :disabled="deletingId !== null && deletingId !== row.id"
|
|
|
|
|
+ @click="handleDelete(row)"
|
|
|
|
|
+ >删除</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ v-model:current-page="query.pageNum"
|
|
|
|
|
+ v-model:page-size="query.pageSize"
|
|
|
|
|
+ :total="page.total"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next"
|
|
|
|
|
+ :page-sizes="[10, 20, 50]"
|
|
|
|
|
+ @change="loadRules"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-drawer
|
|
|
|
|
+ v-model="drawerVisible"
|
|
|
|
|
+ size="520px"
|
|
|
|
|
+ direction="rtl"
|
|
|
|
|
+ class="detail-drawer threshold-drawer"
|
|
|
|
|
+ :title="form.id ? '编辑阈值' : '新增阈值'"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ v-if="deviceLoadError"
|
|
|
|
|
+ class="load-error"
|
|
|
|
|
+ type="error"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ title="供水设备加载失败"
|
|
|
|
|
+ :description="deviceLoadError"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button size="small" type="primary" @click="loadDevices">重试</el-button>
|
|
|
|
|
+ </el-alert>
|
|
|
|
|
+ <el-form :model="form" label-width="100px">
|
|
|
|
|
+ <el-form-item label="监测设备">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-if="!form.id"
|
|
|
|
|
+ v-model="form.deviceCodes"
|
|
|
|
|
+ multiple
|
|
|
|
|
+ filterable
|
|
|
|
|
+ placeholder="请选择供水监测设备"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="device in devices"
|
|
|
|
|
+ :key="device.equipmentId || device.id"
|
|
|
|
|
+ :label="device.equipmentName ? `${device.equipmentName}(${device.equipmentCode})` : device.equipmentCode"
|
|
|
|
|
+ :value="device.equipmentCode"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <el-select v-else v-model="form.deviceCode" filterable placeholder="请选择供水监测设备">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="device in devices"
|
|
|
|
|
+ :key="device.equipmentId || device.id"
|
|
|
|
|
+ :label="device.equipmentName ? `${device.equipmentName}(${device.equipmentCode})` : device.equipmentCode"
|
|
|
|
|
+ :value="device.equipmentCode"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="预警分类">
|
|
|
|
|
+ <el-select v-model="form.warningType" filterable allow-create placeholder="请选择预警分类">
|
|
|
|
|
+ <el-option v-for="item in warningTypes" :key="item" :label="item" :value="item" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="预警编码">
|
|
|
|
|
+ <el-input v-model="form.warningCode" placeholder="例如 WARN-T5-FLOW" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="阈值下限">
|
|
|
|
|
+ <el-input-number v-model="form.minValue" :precision="3" placeholder="可留空" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="阈值上限">
|
|
|
|
|
+ <el-input-number v-model="form.maxValue" :precision="3" placeholder="可留空" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="分时生效">
|
|
|
|
|
+ <div class="period-range">
|
|
|
|
|
+ <el-time-picker v-model="form.periodStart" format="HH:mm" value-format="HH:mm" placeholder="全天开始" />
|
|
|
|
|
+ <span>至</span>
|
|
|
|
|
+ <el-time-picker v-model="form.periodEnd" format="HH:mm" value-format="HH:mm" placeholder="全天结束" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="备注">
|
|
|
|
|
+ <el-input v-model="form.remark" type="textarea" :rows="3" maxlength="200" show-word-limit />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="drawerVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="saving" @click="saveThreshold">保存</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-drawer>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { computed, onMounted, ref } from 'vue'
|
|
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
+import { Bell, CircleCheck, Clock, Connection, DataLine, Delete, Edit, Plus, Refresh, Search, Warning } from '@element-plus/icons-vue'
|
|
|
|
|
+import { deleteThresholds, listThresholdDevices, listThresholds, saveThresholds, updateThreshold } from '@/api/pipeNetwork/waterSupply'
|
|
|
|
|
+import { normalizePage, unwrapResponse } from '@/utils/waterSupplyModel'
|
|
|
|
|
+import { buildThresholdQuery, buildThresholdSavePayload, buildThresholdUpdatePayload } from '@/utils/waterSupplyUiModel'
|
|
|
|
|
+import { getRequestErrorMessage } from '@/utils/waterFacilityQuery'
|
|
|
|
|
+
|
|
|
|
|
+const warningTypes = ['压力预警', '流量预警', '漏失预警', '设备离线', '水质异常']
|
|
|
|
|
+const query = ref({ pageNum: 1, pageSize: 10, deviceCode: '', warningType: undefined })
|
|
|
|
|
+const page = ref({ records: [], total: 0 })
|
|
|
|
|
+const devices = ref([])
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const saving = ref(false)
|
|
|
|
|
+const deletingId = ref(null)
|
|
|
|
|
+const loadError = ref('')
|
|
|
|
|
+const deviceLoadError = ref('')
|
|
|
|
|
+const drawerVisible = ref(false)
|
|
|
|
|
+const form = ref({})
|
|
|
|
|
+
|
|
|
|
|
+const allDayCount = computed(() => page.value.records.filter(item => !isTimed(item)).length)
|
|
|
|
|
+const timedCount = computed(() => page.value.records.filter(isTimed).length)
|
|
|
|
|
+
|
|
|
|
|
+const thresholdText = row => row.minValue != null || row.maxValue != null
|
|
|
|
|
+ ? `${row.minValue ?? '-'} ~ ${row.maxValue ?? '-'}`
|
|
|
|
|
+ : '-'
|
|
|
|
|
+const isTimed = row => Boolean(row.periodStart || row.periodEnd)
|
|
|
|
|
+const periodText = row => isTimed(row)
|
|
|
|
|
+ ? `${row.periodStart || '--:--'} - ${row.periodEnd || '--:--'}`
|
|
|
|
|
+ : '全天'
|
|
|
|
|
+
|
|
|
|
|
+async function loadRules() {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ page.value = normalizePage(await listThresholds(buildThresholdQuery(query.value)))
|
|
|
|
|
+ loadError.value = ''
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ loadError.value = getRequestErrorMessage(error, '请检查服务状态')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadDevices() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = unwrapResponse(await listThresholdDevices())
|
|
|
|
|
+ devices.value = Array.isArray(response) ? response : []
|
|
|
|
|
+ deviceLoadError.value = ''
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ deviceLoadError.value = getRequestErrorMessage(error, '请检查服务状态')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleSearch() {
|
|
|
|
|
+ query.value.pageNum = 1
|
|
|
|
|
+ loadRules()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetQuery() {
|
|
|
|
|
+ query.value = { pageNum: 1, pageSize: 10, deviceCode: '', warningType: undefined }
|
|
|
|
|
+ loadRules()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openCreate() {
|
|
|
|
|
+ form.value = {
|
|
|
|
|
+ deviceCodes: [],
|
|
|
|
|
+ warningType: '压力预警',
|
|
|
|
|
+ warningCode: '',
|
|
|
|
|
+ minValue: undefined,
|
|
|
|
|
+ maxValue: undefined,
|
|
|
|
|
+ periodStart: '',
|
|
|
|
|
+ periodEnd: '',
|
|
|
|
|
+ remark: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ drawerVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openEdit(row) {
|
|
|
|
|
+ form.value = { ...row }
|
|
|
|
|
+ drawerVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function validateForm() {
|
|
|
|
|
+ const current = form.value
|
|
|
|
|
+ const selectedDevices = current.id ? [current.deviceCode] : current.deviceCodes
|
|
|
|
|
+ if (!selectedDevices?.filter(Boolean).length) return '请选择至少一个供水监测设备'
|
|
|
|
|
+ if (!current.warningType?.trim()) return '请选择预警分类'
|
|
|
|
|
+ if (!current.warningCode?.trim()) return '请填写预警编码'
|
|
|
|
|
+ if (current.minValue == null && current.maxValue == null) return '阈值下限和上限至少填写一项'
|
|
|
|
|
+ if (current.minValue != null && current.maxValue != null && Number(current.minValue) > Number(current.maxValue)) return '阈值下限不能大于上限'
|
|
|
|
|
+ if (Boolean(current.periodStart) !== Boolean(current.periodEnd)) return '分时开始和结束时间必须同时填写'
|
|
|
|
|
+ return ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function saveThreshold() {
|
|
|
|
|
+ const message = validateForm()
|
|
|
|
|
+ if (message) {
|
|
|
|
|
+ ElMessage.warning(message)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ saving.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (form.value.id) {
|
|
|
|
|
+ await updateThreshold(buildThresholdUpdatePayload(form.value))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await saveThresholds(buildThresholdSavePayload(form.value))
|
|
|
|
|
+ }
|
|
|
|
|
+ ElMessage.success('阈值已保存')
|
|
|
|
|
+ drawerVisible.value = false
|
|
|
|
|
+ await loadRules()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ ElMessage.error(getRequestErrorMessage(error, '阈值保存失败,请检查权限或服务状态'))
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ saving.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function handleDelete(row) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await ElMessageBox.confirm(`确认删除设备 ${row.deviceCode} 的阈值规则吗?`, '删除确认', { type: 'warning' })
|
|
|
|
|
+ deletingId.value = row.id
|
|
|
|
|
+ await deleteThresholds([row.id])
|
|
|
|
|
+ ElMessage.success('阈值已删除')
|
|
|
|
|
+ await loadRules()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ if (error !== 'cancel') {
|
|
|
|
|
+ ElMessage.error(getRequestErrorMessage(error, '阈值删除失败,请检查权限或服务状态'))
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ deletingId.value = null
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ loadRules()
|
|
|
|
|
+ loadDevices()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss">
|
|
|
|
|
+@use './waterSupplyPageTheme.scss';
|
|
|
|
|
+</style>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.threshold-page {
|
|
|
|
|
+ background: linear-gradient(135deg, #eff6ff 0%, #f8fafc 45%, #eef2f7 100%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.heading-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-title-icon {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ place-items: center;
|
|
|
|
|
+ width: 42px;
|
|
|
|
|
+ height: 42px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ background: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ box-shadow: 0 8px 16px rgba(37, 99, 235, 0.2);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric-grid {
|
|
|
|
|
+ grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+ min-height: 78px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ box-shadow: 0 6px 14px rgba(15, 23, 42, 0.04);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric-icon {
|
|
|
|
|
+ position: static;
|
|
|
|
|
+ flex: 0 0 42px;
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ place-items: center;
|
|
|
|
|
+ width: 42px;
|
|
|
|
|
+ height: 42px;
|
|
|
|
|
+ color: #2563eb;
|
|
|
|
|
+ background: linear-gradient(135deg, #dbeafe 0%, #eff6ff 100%);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric-icon :deep(.el-icon) {
|
|
|
|
|
+ font-size: 22px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric-body span {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ margin-bottom: 3px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ color: #64748b;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric-value {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: baseline;
|
|
|
|
|
+ gap: 5px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.metric-value strong {
|
|
|
|
|
+ color: #1e293b;
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ line-height: 1.2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filter-card,
|
|
|
|
|
+.table-card {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ box-shadow: 0 6px 14px rgba(15, 23, 42, 0.04);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-title {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ color: #1e293b;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-title :deep(.el-icon) {
|
|
|
|
|
+ color: #2563eb;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filter-form :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.warning-select {
|
|
|
|
|
+ width: 220px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.load-error {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.threshold-range {
|
|
|
|
|
+ color: #17324d;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.table-card :deep(.el-card__body) {
|
|
|
|
|
+ padding-top: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.table-card :deep(.el-table__header th) {
|
|
|
|
|
+ color: #475569;
|
|
|
|
|
+ background: #f8fafc;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.table-card :deep(.el-pagination) {
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ margin-top: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.threshold-drawer :deep(.el-select),
|
|
|
|
|
+.threshold-drawer :deep(.el-input-number) {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.period-range {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.period-range :deep(.el-date-editor) {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (max-width: 1000px) {
|
|
|
|
|
+ .metric-grid {
|
|
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (max-width: 650px) {
|
|
|
|
|
+ .page-heading,
|
|
|
|
|
+ .heading-actions {
|
|
|
|
|
+ align-items: stretch;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .metric-grid {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .threshold-drawer {
|
|
|
|
|
+ width: 100% !important;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|