feat 异常警告

This commit is contained in:
Ironsp
2026-09-14 15:10:48 +08:00
parent 3d857002e0
commit dd7c8b4162
9 changed files with 443 additions and 68 deletions

View File

@@ -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>

View File

@@ -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">
<view class="warning-title-row">
<text class="warning-title"> <text class="warning-title">
钢轮温度过高 {{ title }}
</text> </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>

View File

@@ -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;
} }

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'],
} }

View File

@@ -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: '设备故障,已登记报修',

View File

@@ -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 // 现场处置记录