feat 异常警告
This commit is contained in:
@@ -1,9 +1,31 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { WarningRecord } from '@/service/warningRecord'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useDbStore } from '@/store/modules/db'
|
||||||
|
|
||||||
|
const db = useDbStore()
|
||||||
|
|
||||||
|
/** 首页只展示最近几条 */
|
||||||
|
const list = computed(() => (db.warning_record || []).slice(0, 3))
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!db.warning_record?.length)
|
||||||
|
await db.loadAll()
|
||||||
|
})
|
||||||
|
|
||||||
function goToWarningList() {
|
function goToWarningList() {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/warning/index',
|
url: '/pages/warning/index',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goToDetail(item: WarningRecord) {
|
||||||
|
if (!item.id)
|
||||||
|
return
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/warning/detail/index?id=${item.id}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -17,7 +39,15 @@ function goToWarningList() {
|
|||||||
</wd-button>
|
</wd-button>
|
||||||
</view>
|
</view>
|
||||||
<view class="card-body">
|
<view class="card-body">
|
||||||
<home-warning_list />
|
<view v-if="!list.length" class="empty-tip">
|
||||||
|
暂无异常警告
|
||||||
|
</view>
|
||||||
|
<home-warning_list
|
||||||
|
v-for="item in list"
|
||||||
|
:key="item.id"
|
||||||
|
:item="item"
|
||||||
|
@tap="goToDetail(item)"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -47,4 +77,11 @@ function goToWarningList() {
|
|||||||
.card-body {
|
.card-body {
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999999;
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,16 +1,85 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { WarningRecord } from '@/service/warningRecord'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
item: WarningRecord
|
||||||
|
}>()
|
||||||
|
|
||||||
|
/** 异常类型:0 数据中断 / 1 高温异常 / 2 定位异常 */
|
||||||
|
const exceptionTypeMap: Record<number, string> = {
|
||||||
|
0: '数据中断',
|
||||||
|
1: '高温异常',
|
||||||
|
2: '定位异常',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 异常级别:0 提醒 / 1 警告 / 2 严重 */
|
||||||
|
const exceptionLevelMap: Record<number, string> = {
|
||||||
|
0: '提醒',
|
||||||
|
1: '警告',
|
||||||
|
2: '严重',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处置类型:0 未处理 / 1 现场恢复 / 2 需要保修 */
|
||||||
|
const disposalTypeMap: Record<number, string> = {
|
||||||
|
0: '未处理',
|
||||||
|
1: '现场恢复',
|
||||||
|
2: '需要保修',
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = computed(() => exceptionTypeMap[props.item.exception_type ?? -1] || '未知异常')
|
||||||
|
|
||||||
|
const levelText = computed(() => exceptionLevelMap[props.item.exception_level ?? -1] || '')
|
||||||
|
|
||||||
|
const desc = computed(() => {
|
||||||
|
const device = props.item.device_name || '未知设备'
|
||||||
|
const site = props.item.site_description || ''
|
||||||
|
const disposal = disposalTypeMap[props.item.disposal_type ?? -1]
|
||||||
|
return disposal ? `${device} ${site}(${disposal})` : `${device} ${site}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const timeText = computed(() => formatOccurredAt(props.item.occurred_at))
|
||||||
|
|
||||||
|
function formatOccurredAt(value?: string) {
|
||||||
|
if (!value)
|
||||||
|
return ''
|
||||||
|
const date = new Date(value.replace(/-/g, '/'))
|
||||||
|
if (Number.isNaN(date.getTime()))
|
||||||
|
return value
|
||||||
|
|
||||||
|
const diffMs = Date.now() - date.getTime()
|
||||||
|
const diffMin = Math.floor(diffMs / 60000)
|
||||||
|
if (diffMin < 1)
|
||||||
|
return '刚刚'
|
||||||
|
if (diffMin < 60)
|
||||||
|
return `${diffMin}分钟前`
|
||||||
|
const diffHour = Math.floor(diffMin / 60)
|
||||||
|
if (diffHour < 24)
|
||||||
|
return `${diffHour}小时前`
|
||||||
|
const diffDay = Math.floor(diffHour / 24)
|
||||||
|
if (diffDay < 7)
|
||||||
|
return `${diffDay}天前`
|
||||||
|
return value.slice(0, 16)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="warning-item">
|
<view class="warning-item">
|
||||||
<view class="warning-icon" />
|
<view class="warning-icon" :class="`level-${item.exception_level ?? 0}`" />
|
||||||
<view class="warning-info">
|
<view class="warning-info">
|
||||||
<text class="warning-title">
|
<view class="warning-title-row">
|
||||||
钢轮温度过高
|
<text class="warning-title">
|
||||||
</text>
|
{{ title }}
|
||||||
|
</text>
|
||||||
|
<text v-if="levelText" class="warning-level" :class="`level-${item.exception_level ?? 0}`">
|
||||||
|
{{ levelText }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
<text class="warning-desc">
|
<text class="warning-desc">
|
||||||
徐工XS65H #01 钢轮温度达90°C,超过國值85°C
|
{{ desc }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="warning-time">
|
<text class="warning-time">
|
||||||
10分钟前
|
{{ timeText }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -33,28 +102,74 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #fff2e8;
|
background-color: #fff2e8;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon.level-0 {
|
||||||
|
background-color: #e8f3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon.level-1 {
|
||||||
|
background-color: #fff2e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon.level-2 {
|
||||||
|
background-color: #ffece8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-info {
|
.warning-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-title {
|
.warning-title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
margin-bottom: 4px;
|
}
|
||||||
|
|
||||||
|
.warning-level {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-level.level-0 {
|
||||||
|
color: #3b82f6;
|
||||||
|
background-color: #e8f3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-level.level-1 {
|
||||||
|
color: #f59e0b;
|
||||||
|
background-color: #fff2e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-level.level-2 {
|
||||||
|
color: #ef4444;
|
||||||
|
background-color: #ffece8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-desc {
|
.warning-desc {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #999999;
|
color: #999999;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-time {
|
.warning-time {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #cccccc;
|
color: #cccccc;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const activeTab = ref(0)
|
defineProps<{
|
||||||
const dateRange = ref('')
|
totalCount: number
|
||||||
const level = ref('')
|
unhandledCount: number
|
||||||
|
}>()
|
||||||
|
const activeTab = defineModel<number>('activeTab', { default: 0 })
|
||||||
|
const level = defineModel<number | null>('level', { default: null })
|
||||||
|
const dateRange = defineModel<string>('dateRange', { default: '' })
|
||||||
|
|
||||||
const tabs = ['全部', '定位异常', '高温异常']
|
const tabs = ['全部', '数据中断', '高温异常', '定位异常']
|
||||||
|
|
||||||
const dateOptions = [
|
const dateOptions = [
|
||||||
|
{ value: '', text: '全部时间' },
|
||||||
{ value: 'today', text: '今天' },
|
{ value: 'today', text: '今天' },
|
||||||
{ value: 'yesterday', text: '昨天' },
|
{ value: 'yesterday', text: '昨天' },
|
||||||
{ value: 'week', text: '本周' },
|
{ value: 'week', text: '本周' },
|
||||||
@@ -13,19 +18,24 @@ const dateOptions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const levelOptions = [
|
const levelOptions = [
|
||||||
{ value: 'all', text: '全部级别' },
|
{ value: -1, text: '全部级别' },
|
||||||
{ value: 'critical', text: '严重' },
|
{ value: 2, text: '严重' },
|
||||||
{ value: 'warning', text: '警告' },
|
{ value: 1, text: '警告' },
|
||||||
{ value: 'info', text: '提醒' },
|
{ value: 0, text: '提醒' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const totalCount = ref(8)
|
/** uni-data-select 用 -1 表示全部,再转成 null */
|
||||||
const unhandledCount = ref(3)
|
const levelSelect = computed({
|
||||||
|
get: () => (level.value === null ? -1 : level.value),
|
||||||
|
set: (val: number) => {
|
||||||
|
level.value = val === -1 || val === null || val === undefined ? null : Number(val)
|
||||||
|
},
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="warning-filter">
|
<view class="warning-filter">
|
||||||
<!-- 标签页 -->
|
<!-- 标签页:按异常类型筛选 -->
|
||||||
<view class="tabs">
|
<view class="tabs">
|
||||||
<view
|
<view
|
||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
@@ -40,10 +50,10 @@ const unhandledCount = ref(3)
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 筛选条件 -->
|
<!-- 筛选条件:时间 + 异常级别 -->
|
||||||
<view class="filters">
|
<view class="filters">
|
||||||
<uni-data-select v-model="dateRange" :localdata="dateOptions" />
|
<uni-data-select v-model="dateRange" :localdata="dateOptions" placeholder="全部时间" />
|
||||||
<uni-data-select v-model="level" :localdata="levelOptions" />
|
<uni-data-select v-model="levelSelect" :localdata="levelOptions" placeholder="全部级别" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -69,7 +79,7 @@ const unhandledCount = ref(3)
|
|||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +87,7 @@ const unhandledCount = ref(3)
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 12px 0;
|
padding: 10px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +96,7 @@ const unhandledCount = ref(3)
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-text {
|
.tab-text {
|
||||||
font-size: 18px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,18 +5,24 @@ const props = defineProps<{
|
|||||||
deviceName: string
|
deviceName: string
|
||||||
tags: string[]
|
tags: string[]
|
||||||
time: string
|
time: string
|
||||||
trigger: string
|
|
||||||
status?: '未处理' | '已处理'
|
status?: '未处理' | '已处理'
|
||||||
index: number
|
index: number
|
||||||
|
warningId?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
function goToDetail() {
|
function goToDetail() {
|
||||||
|
if (props.warningId) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/warning/detail/index?id=${props.warningId}`,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const key = `warning_${Date.now()}`
|
const key = `warning_${Date.now()}`
|
||||||
storage.set(key, {
|
storage.set(key, {
|
||||||
deviceName: props.deviceName,
|
deviceName: props.deviceName,
|
||||||
tags: props.tags,
|
tags: props.tags,
|
||||||
time: props.time,
|
time: props.time,
|
||||||
trigger: props.trigger,
|
|
||||||
status: props.status || '未处理',
|
status: props.status || '未处理',
|
||||||
})
|
})
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
@@ -37,7 +43,7 @@ function goToDetail() {
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="tags">
|
<view class="tags">
|
||||||
<view v-for="(tag, index) in tags" :key="index" class="tag">
|
<view v-for="(tag, i) in tags" :key="i" class="tag">
|
||||||
<text class="tag-text">
|
<text class="tag-text">
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</text>
|
</text>
|
||||||
@@ -47,9 +53,6 @@ function goToDetail() {
|
|||||||
<text class="time">
|
<text class="time">
|
||||||
{{ time }}
|
{{ time }}
|
||||||
</text>
|
</text>
|
||||||
<text class="trigger">
|
|
||||||
{{ trigger }}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -112,9 +115,4 @@ function goToDetail() {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trigger {
|
|
||||||
font-size: 18px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { WarningRecord } from '@/service/warningRecord'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { warningRecordService } from '@/service'
|
||||||
import storage from '@/utils/storage'
|
import storage from '@/utils/storage'
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
@@ -8,9 +12,30 @@ definePage({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const warningData = ref<any>(null)
|
/** 异常类型 / 级别 / 处置类型文案 */
|
||||||
|
const exceptionTypeMap: Record<number, string> = {
|
||||||
|
0: '数据中断',
|
||||||
|
1: '高温异常',
|
||||||
|
2: '定位异常',
|
||||||
|
}
|
||||||
|
const exceptionLevelMap: Record<number, string> = {
|
||||||
|
0: '提醒',
|
||||||
|
1: '警告',
|
||||||
|
2: '严重',
|
||||||
|
}
|
||||||
|
const disposalTypeMap: Record<number, string> = {
|
||||||
|
0: '未处理',
|
||||||
|
1: '现场恢复',
|
||||||
|
2: '需要保修',
|
||||||
|
}
|
||||||
|
|
||||||
onLoad((options) => {
|
const warningData = ref<WarningRecord | null>(null)
|
||||||
|
|
||||||
|
onLoad(async (options) => {
|
||||||
|
if (options?.id) {
|
||||||
|
warningData.value = await warningRecordService.selectById(Number(options.id))
|
||||||
|
return
|
||||||
|
}
|
||||||
if (options?.key) {
|
if (options?.key) {
|
||||||
warningData.value = storage.get(options.key)
|
warningData.value = storage.get(options.key)
|
||||||
storage.remove(options.key)
|
storage.remove(options.key)
|
||||||
@@ -23,10 +48,66 @@ onLoad((options) => {
|
|||||||
<back-nav title="警告详情" />
|
<back-nav title="警告详情" />
|
||||||
|
|
||||||
<view class="content">
|
<view class="content">
|
||||||
<view v-if="warningData" class="detail-raw">
|
<view v-if="warningData" class="detail-card">
|
||||||
<text class="raw-text">
|
<view class="row">
|
||||||
{{ JSON.stringify(warningData, null, 2) }}
|
<text class="label">
|
||||||
</text>
|
设备名称
|
||||||
|
</text>
|
||||||
|
<text class="value">
|
||||||
|
{{ warningData.device_name || '-' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="row">
|
||||||
|
<text class="label">
|
||||||
|
异常类型
|
||||||
|
</text>
|
||||||
|
<text class="value">
|
||||||
|
{{ exceptionTypeMap[warningData.exception_type ?? -1] || '-' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="row">
|
||||||
|
<text class="label">
|
||||||
|
异常级别
|
||||||
|
</text>
|
||||||
|
<text class="value">
|
||||||
|
{{ exceptionLevelMap[warningData.exception_level ?? -1] || '-' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="row">
|
||||||
|
<text class="label">
|
||||||
|
发生时间
|
||||||
|
</text>
|
||||||
|
<text class="value">
|
||||||
|
{{ warningData.occurred_at || '-' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="row">
|
||||||
|
<text class="label">
|
||||||
|
现场描述
|
||||||
|
</text>
|
||||||
|
<text class="value">
|
||||||
|
{{ warningData.site_description || '-' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="row">
|
||||||
|
<text class="label">
|
||||||
|
处置类型
|
||||||
|
</text>
|
||||||
|
<text class="value">
|
||||||
|
{{ disposalTypeMap[warningData.disposal_type ?? -1] || '-' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="row">
|
||||||
|
<text class="label">
|
||||||
|
处置记录
|
||||||
|
</text>
|
||||||
|
<text class="value">
|
||||||
|
{{ warningData.disposal_record || '-' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-tip">
|
||||||
|
未找到告警详情
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -43,16 +124,38 @@ onLoad((options) => {
|
|||||||
padding-top: 72px;
|
padding-top: 72px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-raw {
|
.detail-card {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.raw-text {
|
.row {
|
||||||
font-size: 18px;
|
display: flex;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
width: 90px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 16px;
|
||||||
color: #333;
|
color: #333;
|
||||||
font-family: monospace;
|
}
|
||||||
white-space: pre-wrap;
|
|
||||||
|
.empty-tip {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
padding: 24px 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { WarningRecord } from '@/service/warningRecord'
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { useDbStore } from '@/store/modules/db'
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
layout: false,
|
layout: false,
|
||||||
style: {
|
style: {
|
||||||
@@ -6,22 +10,107 @@ definePage({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const warnings = [
|
const db = useDbStore()
|
||||||
{
|
|
||||||
deviceName: '徐工XS65H #01',
|
/** 异常类型:0 数据中断 / 1 高温异常 / 2 定位异常 */
|
||||||
tags: ['定位异常', '警告'],
|
const exceptionTypeMap: Record<number, string> = {
|
||||||
time: '14:10:04·已持续5分30秒',
|
0: '数据中断',
|
||||||
trigger: '触发阈值:RTK信号弱,精度下降',
|
1: '高温异常',
|
||||||
status: '未处理' as const,
|
2: '定位异常',
|
||||||
},
|
}
|
||||||
{
|
|
||||||
deviceName: '徐工XS65H #01',
|
/** 异常级别:0 提醒 / 1 警告 / 2 严重 */
|
||||||
tags: ['定位异常', '警告'],
|
const exceptionLevelMap: Record<number, string> = {
|
||||||
time: '14:10:04·已持续5分30秒',
|
0: '提醒',
|
||||||
trigger: '触发阈值:RTK信号弱,精度下降',
|
1: '警告',
|
||||||
status: '未处理' as const,
|
2: '严重',
|
||||||
},
|
}
|
||||||
]
|
|
||||||
|
const activeTab = ref(0) // 0全部 1数据中断 2高温异常 3定位异常
|
||||||
|
const levelFilter = ref<number | null>(null) // null全部 / 0提醒 / 1警告 / 2严重
|
||||||
|
const dateRange = ref('') // ''全部 / today / yesterday / week / month
|
||||||
|
|
||||||
|
const totalCount = computed(() => db.warning_record.length)
|
||||||
|
const unhandledCount = computed(() =>
|
||||||
|
db.warning_record.filter(item => item.disposal_type === 0).length,
|
||||||
|
)
|
||||||
|
|
||||||
|
const warnings = computed(() => {
|
||||||
|
return db.warning_record
|
||||||
|
.filter((item) => {
|
||||||
|
if (activeTab.value > 0 && item.exception_type !== activeTab.value - 1)
|
||||||
|
return false
|
||||||
|
if (levelFilter.value !== null && item.exception_level !== levelFilter.value)
|
||||||
|
return false
|
||||||
|
if (dateRange.value && !matchDateRange(item.occurred_at, dateRange.value))
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.map(mapWarningItem)
|
||||||
|
})
|
||||||
|
|
||||||
|
function parseDate(value?: string) {
|
||||||
|
if (!value)
|
||||||
|
return null
|
||||||
|
const date = new Date(value.replace(/-/g, '/'))
|
||||||
|
return Number.isNaN(date.getTime()) ? null : date
|
||||||
|
}
|
||||||
|
|
||||||
|
function startOfDay(date: Date) {
|
||||||
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按今天 / 昨天 / 本周 / 本月筛选发生时间 */
|
||||||
|
function matchDateRange(occurredAt: string | undefined, range: string) {
|
||||||
|
const occurred = parseDate(occurredAt)
|
||||||
|
if (!occurred)
|
||||||
|
return false
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const todayStart = startOfDay(now)
|
||||||
|
const occurredStart = startOfDay(occurred)
|
||||||
|
|
||||||
|
if (range === 'today')
|
||||||
|
return occurredStart.getTime() === todayStart.getTime()
|
||||||
|
|
||||||
|
if (range === 'yesterday') {
|
||||||
|
const yesterday = new Date(todayStart)
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1)
|
||||||
|
return occurredStart.getTime() === yesterday.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range === 'week') {
|
||||||
|
// 本周一 00:00 起
|
||||||
|
const weekStart = new Date(todayStart)
|
||||||
|
const day = weekStart.getDay() || 7 // 周日按 7
|
||||||
|
weekStart.setDate(weekStart.getDate() - day + 1)
|
||||||
|
return occurredStart.getTime() >= weekStart.getTime() && occurredStart.getTime() <= todayStart.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range === 'month') {
|
||||||
|
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||||
|
return occurredStart.getTime() >= monthStart.getTime() && occurredStart.getTime() <= todayStart.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapWarningItem(item: WarningRecord) {
|
||||||
|
const typeText = exceptionTypeMap[item.exception_type ?? -1] || '未知异常'
|
||||||
|
const levelText = exceptionLevelMap[item.exception_level ?? -1] || ''
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
deviceName: item.device_name || '未知设备',
|
||||||
|
tags: [typeText, levelText].filter(Boolean),
|
||||||
|
time: item.occurred_at || '',
|
||||||
|
status: (item.disposal_type === 0 ? '未处理' : '已处理') as '未处理' | '已处理',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!db.warning_record.length)
|
||||||
|
await db.loadAll()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -29,17 +118,26 @@ const warnings = [
|
|||||||
<back-nav title="异常警告" />
|
<back-nav title="异常警告" />
|
||||||
|
|
||||||
<view class="content">
|
<view class="content">
|
||||||
<warning-filter />
|
<warning-filter
|
||||||
|
v-model:active-tab="activeTab"
|
||||||
|
v-model:level="levelFilter"
|
||||||
|
v-model:date-range="dateRange"
|
||||||
|
:total-count="totalCount"
|
||||||
|
:unhandled-count="unhandledCount"
|
||||||
|
/>
|
||||||
|
|
||||||
<view class="warning-list">
|
<view class="warning-list">
|
||||||
|
<view v-if="!warnings.length" class="empty-tip">
|
||||||
|
暂无异常警告
|
||||||
|
</view>
|
||||||
<warning-item
|
<warning-item
|
||||||
v-for="(item, index) in warnings"
|
v-for="(item, index) in warnings"
|
||||||
:key="index"
|
:key="item.id ?? index"
|
||||||
:index="index"
|
:index="index"
|
||||||
|
:warning-id="item.id"
|
||||||
:device-name="item.deviceName"
|
:device-name="item.deviceName"
|
||||||
:tags="item.tags"
|
:tags="item.tags"
|
||||||
:time="item.time"
|
:time="item.time"
|
||||||
:trigger="item.trigger"
|
|
||||||
:status="item.status"
|
:status="item.status"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
@@ -63,4 +161,11 @@ const warnings = [
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const expectedColumns: Record<string, string[]> = {
|
|||||||
project: ['id', 'project_name', 'stake_range', 'total_area', 'status', 'created_at', 'updated_at'],
|
project: ['id', 'project_name', 'stake_range', 'total_area', 'status', 'created_at', 'updated_at'],
|
||||||
work_record: ['id', 'project_id', 'start_time', 'end_time', 'compaction_area', 'pass_count', 'created_at'],
|
work_record: ['id', 'project_id', 'start_time', 'end_time', 'compaction_area', 'pass_count', 'created_at'],
|
||||||
rtk_track: ['id', 'work_record_id', 'latitude', 'longitude', 'speed', 'heading', 'steel_wheel_temp', 'compaction_value', 'created_at'],
|
rtk_track: ['id', 'work_record_id', 'latitude', 'longitude', 'speed', 'heading', 'steel_wheel_temp', 'compaction_value', 'created_at'],
|
||||||
warning_record: ['id', 'project_id', 'device_name', 'exception_type', 'occurred_at', 'site_description', 'disposal_record', 'disposal_type', 'is_read', 'created_at'],
|
warning_record: ['id', 'project_id', 'device_name', 'exception_type', 'exception_level', 'occurred_at', 'site_description', 'disposal_record', 'disposal_type', 'is_read', 'created_at'],
|
||||||
history_report: ['id', 'project_id', 'report_name', 'device_id', 'construction_area', 'start_time', 'end_time', 'duration', 'compaction_area', 'pass_count', 'qualified_rate', 'avg_speed', 'avg_paving_temp', 'avg_compaction', 'exception_count', 'created_at'],
|
history_report: ['id', 'project_id', 'report_name', 'device_id', 'construction_area', 'start_time', 'end_time', 'duration', 'compaction_area', 'pass_count', 'qualified_rate', 'avg_speed', 'avg_paving_temp', 'avg_compaction', 'exception_count', 'created_at'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,9 @@ async function seedRtkTrack() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入告警记录假数据(3条)
|
* 插入告警记录假数据(3条)
|
||||||
* 字段:project_id / device_name / exception_type(0数据中断 1高温异常 2定位异常)
|
* 字段:project_id / device_name
|
||||||
|
* exception_type(0数据中断 1高温异常 2定位异常)
|
||||||
|
* exception_level(0提醒 1警告 2严重)
|
||||||
* occurred_at / site_description / disposal_record
|
* occurred_at / site_description / disposal_record
|
||||||
* disposal_type(0未处理 1现场恢复 2需要保修) / is_read
|
* disposal_type(0未处理 1现场恢复 2需要保修) / is_read
|
||||||
*/
|
*/
|
||||||
@@ -117,6 +119,7 @@ async function seedWarningRecord() {
|
|||||||
project_id: 1,
|
project_id: 1,
|
||||||
device_name: 'XS-65H',
|
device_name: 'XS-65H',
|
||||||
exception_type: 0,
|
exception_type: 0,
|
||||||
|
exception_level: 1,
|
||||||
occurred_at: '2026-09-10 09:15:00',
|
occurred_at: '2026-09-10 09:15:00',
|
||||||
site_description: '设备上报数据中断超过 30 秒',
|
site_description: '设备上报数据中断超过 30 秒',
|
||||||
disposal_record: '',
|
disposal_record: '',
|
||||||
@@ -127,6 +130,7 @@ async function seedWarningRecord() {
|
|||||||
project_id: 2,
|
project_id: 2,
|
||||||
device_name: 'XS-65H',
|
device_name: 'XS-65H',
|
||||||
exception_type: 1,
|
exception_type: 1,
|
||||||
|
exception_level: 2,
|
||||||
occurred_at: '2026-09-09 10:20:00',
|
occurred_at: '2026-09-09 10:20:00',
|
||||||
site_description: '钢轮温度超过安全阈值',
|
site_description: '钢轮温度超过安全阈值',
|
||||||
disposal_record: '现场降温后恢复作业',
|
disposal_record: '现场降温后恢复作业',
|
||||||
@@ -137,6 +141,7 @@ async function seedWarningRecord() {
|
|||||||
project_id: 1,
|
project_id: 1,
|
||||||
device_name: 'RTK-01',
|
device_name: 'RTK-01',
|
||||||
exception_type: 2,
|
exception_type: 2,
|
||||||
|
exception_level: 0,
|
||||||
occurred_at: '2026-09-10 14:05:00',
|
occurred_at: '2026-09-10 14:05:00',
|
||||||
site_description: 'RTK 定位信号丢失,无法获取有效坐标',
|
site_description: 'RTK 定位信号丢失,无法获取有效坐标',
|
||||||
disposal_record: '设备故障,已登记报修',
|
disposal_record: '设备故障,已登记报修',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export const warningRecordTableSql = `CREATE TABLE IF NOT EXISTS warning_record
|
|||||||
project_id INTEGER,
|
project_id INTEGER,
|
||||||
device_name TEXT,
|
device_name TEXT,
|
||||||
exception_type INTEGER,
|
exception_type INTEGER,
|
||||||
|
exception_level INTEGER DEFAULT 0,
|
||||||
occurred_at DATETIME,
|
occurred_at DATETIME,
|
||||||
site_description TEXT,
|
site_description TEXT,
|
||||||
disposal_record TEXT,
|
disposal_record TEXT,
|
||||||
@@ -22,6 +23,7 @@ export interface WarningRecord {
|
|||||||
project_id: number // 所属项目 ID,关联 project.id
|
project_id: number // 所属项目 ID,关联 project.id
|
||||||
device_name?: string // 设备名称
|
device_name?: string // 设备名称
|
||||||
exception_type?: 0 | 1 | 2 // 异常类型,0:数据中断,1:高温异常,2:定位异常
|
exception_type?: 0 | 1 | 2 // 异常类型,0:数据中断,1:高温异常,2:定位异常
|
||||||
|
exception_level?: 0 | 1 | 2 // 异常级别,0:提醒,1:警告,2:严重
|
||||||
occurred_at?: string // 发生时间
|
occurred_at?: string // 发生时间
|
||||||
site_description?: string // 现场描述
|
site_description?: string // 现场描述
|
||||||
disposal_record?: string // 现场处置记录
|
disposal_record?: string // 现场处置记录
|
||||||
|
|||||||
Reference in New Issue
Block a user