1109 lines
26 KiB
Plaintext
1109 lines
26 KiB
Plaintext
<!-- 养老管理系统 - 事件报告管理 -->
|
||
<template>
|
||
<view class="incident-management">
|
||
<view class="header">
|
||
<text class="title">事件报告</text>
|
||
<button class="add-btn" @click="showAddIncident">新建报告</button>
|
||
</view>
|
||
|
||
<!-- 统计卡片 -->
|
||
<view class="stats-section">
|
||
<view class="stat-card">
|
||
<view class="stat-icon"></view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.total_incidents }}</text>
|
||
<text class="stat-label">总事件数</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon"></view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.high_severity }}</text>
|
||
<text class="stat-label">高严重性</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">⏳</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.pending_incidents }}</text>
|
||
<text class="stat-label">待处理</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">✅</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.resolved_incidents }}</text>
|
||
<text class="stat-label">已解决</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 筛选区域 -->
|
||
<view class="filter-section">
|
||
<view class="filter-item">
|
||
<text class="filter-label">老人:</text>
|
||
<picker-view class="picker" :value="selectedElderIndex" @change="onElderChange">
|
||
<picker-view-column>
|
||
<view v-for="(elder, index) in elderOptions" :key="elder.id" class="picker-item">
|
||
{{ elder.name }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">类型:</text>
|
||
<picker-view class="picker" :value="selectedTypeIndex" @change="onTypeChange">
|
||
<picker-view-column>
|
||
<view v-for="(type, index) in typeOptions" :key="index" class="picker-item">
|
||
{{ type.label }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">严重性:</text>
|
||
<picker-view class="picker" :value="selectedSeverityIndex" @change="onSeverityChange">
|
||
<picker-view-column>
|
||
<view v-for="(severity, index) in severityOptions" :key="index" class="picker-item">
|
||
{{ severity.label }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">状态:</text>
|
||
<picker-view class="picker" :value="selectedStatusIndex" @change="onStatusChange">
|
||
<picker-view-column>
|
||
<view v-for="(status, index) in statusOptions" :key="index" class="picker-item">
|
||
{{ status.label }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<button class="search-btn" @click="searchIncidents">搜索</button>
|
||
</view>
|
||
|
||
<!-- 事件列表 -->
|
||
<view class="incidents-list">
|
||
<view v-for="incident in incidents" :key="incident.id" class="incident-item" @click="viewIncidentDetail(incident)">
|
||
<view class="incident-header">
|
||
<text class="incident-title">{{ incident.title }}</text>
|
||
<view class="severity-badge" :class="getSeverityClass(incident.severity)">
|
||
<text class="severity-text">{{ getSeverityText(incident.severity) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="incident-info">
|
||
<text class="elder-name">{{ getElderName(incident.elder_id) }}</text>
|
||
<text class="incident-type">类型: {{ getTypeText(incident.incident_type) }}</text>
|
||
<text class="incident-location">地点: {{ incident.location ?? '未记录' }}</text>
|
||
</view>
|
||
<view class="incident-time">
|
||
<text class="time-text">发生时间: {{ formatDateTime(incident.incident_time) }}</text>
|
||
<text class="time-text">报告时间: {{ formatDateTime(incident.created_at) }}</text>
|
||
</view>
|
||
<view class="incident-description">
|
||
<text class="description-text">{{ incident.description ?? '无描述' }}</text>
|
||
</view>
|
||
<view class="incident-status">
|
||
<view class="status-badge" :class="getStatusClass(incident.status)">
|
||
<text class="status-text">{{ getStatusText(incident.status) }}</text>
|
||
</view>
|
||
<text class="follow-up" v-if="incident.follow_up_required">需要跟进</text>
|
||
</view>
|
||
<view class="incident-actions">
|
||
<button class="action-btn edit-btn" @click.stop="editIncident(incident)">编辑</button>
|
||
<button class="action-btn resolve-btn" v-if="incident.status !== 'resolved'" @click.stop="resolveIncident(incident)">解决</button>
|
||
<button class="action-btn report-btn" @click.stop="generateReport(incident)">生成报告</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 添加/编辑事件弹窗 -->
|
||
<view v-if="showIncidentModal" class="modal-overlay" @click="closeIncidentModal">
|
||
<view class="modal-content" @click.stop="">
|
||
<view class="modal-header">
|
||
<text class="modal-title">{{ isEditMode ? '编辑事件' : '新建事件报告' }}</text>
|
||
<button class="close-btn" @click="closeIncidentModal">×</button>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="form-group">
|
||
<text class="form-label">老人:</text>
|
||
<picker-view class="form-picker" :value="formData.elderIndex" @change="onFormElderChange">
|
||
<picker-view-column>
|
||
<view v-for="(elder, index) in elderOptions" :key="elder.id" class="picker-item">
|
||
{{ elder.name }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">事件标题:</text>
|
||
<input class="form-input" v-model="formData.title" placeholder="请输入事件标题" />
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">事件类型:</text>
|
||
<picker-view class="form-picker" :value="formData.typeIndex" @change="onFormTypeChange">
|
||
<picker-view-column>
|
||
<view v-for="(type, index) in incidentTypes" :key="index" class="picker-item">
|
||
{{ type.label }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">严重性:</text>
|
||
<picker-view class="form-picker" :value="formData.severityIndex" @change="onFormSeverityChange">
|
||
<picker-view-column>
|
||
<view v-for="(severity, index) in incidentSeverities" :key="index" class="picker-item">
|
||
{{ severity.label }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">发生地点:</text>
|
||
<input class="form-input" v-model="formData.location" placeholder="请输入发生地点" />
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">发生时间:</text>
|
||
<view class="datetime-row">
|
||
<lime-date-time-picker v-model="formData.incident_date" type="date" :placeholder="'选择事件日期'" />
|
||
<lime-date-time-picker v-model="formData.incident_time" type="time" :placeholder="'选择事件时间'" />
|
||
</view>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">事件描述:</text>
|
||
<textarea class="form-textarea" v-model="formData.description" placeholder="请详细描述事件经过"></textarea>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">已采取措施:</text>
|
||
<textarea class="form-textarea" v-model="formData.actions_taken" placeholder="请描述已采取的措施"></textarea>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">见证人:</text>
|
||
<input class="form-input" v-model="formData.witnesses" placeholder="请输入见证人姓名,多人用逗号分隔" />
|
||
</view>
|
||
<view class="form-group">
|
||
<view class="checkbox-item">
|
||
<input type="checkbox" v-model="formData.follow_up_required" />
|
||
<text class="checkbox-label">需要后续跟进</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="modal-footer">
|
||
<button class="cancel-btn" @click="closeIncidentModal">取消</button>
|
||
<button class="save-btn" @click="saveIncident">保存</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import type { Incident, Elder } from '../types.uts'
|
||
import { formatDateTime, getSeverityClass, getStatusClass } from '../types.uts'
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
|
||
|
||
// 统计数据类型
|
||
type IncidentStats = {
|
||
total_incidents: number
|
||
high_severity: number
|
||
pending_incidents: number
|
||
resolved_incidents: number
|
||
}
|
||
|
||
// 响应式数据
|
||
const incidents = ref<Incident[]>([])
|
||
const elderOptions = ref<Elder[]>([])
|
||
const eldersMap = ref<Map<string, string>>(new Map())
|
||
const stats = ref<IncidentStats>({
|
||
total_incidents: 0,
|
||
high_severity: 0,
|
||
pending_incidents: 0,
|
||
resolved_incidents: 0
|
||
})
|
||
|
||
// 筛选相关
|
||
const selectedElderIndex = ref([0])
|
||
const selectedTypeIndex = ref([0])
|
||
const selectedSeverityIndex = ref([0])
|
||
const selectedStatusIndex = ref([0])
|
||
|
||
const typeOptions = [
|
||
{ value: 'all', label: '全部类型' },
|
||
{ value: 'fall', label: '跌倒' },
|
||
{ value: 'medical', label: '医疗事件' },
|
||
{ value: 'behavior', label: '行为事件' },
|
||
{ value: 'medication', label: '用药事件' },
|
||
{ value: 'equipment', label: '设备故障' },
|
||
{ value: 'other', label: '其他事件' }
|
||
]
|
||
|
||
const severityOptions = [
|
||
{ value: 'all', label: '全部级别' },
|
||
{ value: 'low', label: '低' },
|
||
{ value: 'medium', label: '中' },
|
||
{ value: 'high', label: '高' },
|
||
{ value: 'critical', label: '紧急' }
|
||
]
|
||
|
||
const statusOptions = [
|
||
{ value: 'all', label: '全部状态' },
|
||
{ value: 'reported', label: '已报告' },
|
||
{ value: 'investigating', label: '调查中' },
|
||
{ value: 'resolved', label: '已解决' },
|
||
{ value: 'closed', label: '已关闭' }
|
||
]
|
||
|
||
const incidentTypes = [
|
||
{ value: 'fall', label: '跌倒' },
|
||
{ value: 'medical', label: '医疗事件' },
|
||
{ value: 'behavior', label: '行为事件' },
|
||
{ value: 'medication', label: '用药事件' },
|
||
{ value: 'equipment', label: '设备故障' },
|
||
{ value: 'other', label: '其他事件' }
|
||
]
|
||
|
||
const incidentSeverities = [
|
||
{ value: 'low', label: '低' },
|
||
{ value: 'medium', label: '中' },
|
||
{ value: 'high', label: '高' },
|
||
{ value: 'critical', label: '紧急' }
|
||
]
|
||
|
||
// 弹窗相关
|
||
const showIncidentModal = ref(false)
|
||
const isEditMode = ref(false)
|
||
const currentIncidentId = ref<string | null>(null)
|
||
|
||
// 表单数据
|
||
const formData = ref({
|
||
elderIndex: [0],
|
||
title: '',
|
||
typeIndex: [0],
|
||
severityIndex: [0],
|
||
location: '',
|
||
incident_date: '',
|
||
incident_time: '',
|
||
description: '',
|
||
actions_taken: '',
|
||
witnesses: '',
|
||
follow_up_required: false
|
||
})
|
||
|
||
// 页面加载
|
||
onLoad(() => {
|
||
loadData()
|
||
})
|
||
|
||
// 加载数据
|
||
async function loadData(): Promise<void> {
|
||
try {
|
||
await Promise.all([
|
||
loadElders(),
|
||
loadIncidents(),
|
||
loadStats()
|
||
])
|
||
} catch (error) {
|
||
console.error('加载数据失败:', error)
|
||
uni.showToast({
|
||
title: '加载数据失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 加载老人列表
|
||
async function loadElders(): Promise<void> {
|
||
const result = await supa.executeAs<Elder>('eldercare_admin', `
|
||
SELECT id, name FROM ec_elders
|
||
WHERE status = 'active'
|
||
ORDER BY name
|
||
`)
|
||
elderOptions.value = [{ id: '', name: '全部老人' } as Elder, ...result]
|
||
|
||
// 建立映射
|
||
const map = new Map<string, string>()
|
||
for (let i: Int = 0; i < result.length; i++) {
|
||
const elder = result[i]
|
||
map.set(elder.id, elder.name)
|
||
}
|
||
eldersMap.value = map
|
||
}
|
||
|
||
// 加载事件列表
|
||
async function loadIncidents(): Promise<void> {
|
||
let whereClause = "WHERE 1=1"
|
||
|
||
// 老人筛选
|
||
if (selectedElderIndex.value[0] > 0) {
|
||
const selectedElder = elderOptions.value[selectedElderIndex.value[0]]
|
||
whereClause += ` AND elder_id = '${selectedElder.id}'`
|
||
}
|
||
|
||
// 类型筛选
|
||
if (selectedTypeIndex.value[0] > 0) {
|
||
const selectedType = typeOptions[selectedTypeIndex.value[0]]
|
||
whereClause += ` AND incident_type = '${selectedType.value}'`
|
||
}
|
||
|
||
// 严重性筛选
|
||
if (selectedSeverityIndex.value[0] > 0) {
|
||
const selectedSeverity = severityOptions[selectedSeverityIndex.value[0]]
|
||
whereClause += ` AND severity = '${selectedSeverity.value}'`
|
||
}
|
||
|
||
// 状态筛选
|
||
if (selectedStatusIndex.value[0] > 0) {
|
||
const selectedStatus = statusOptions[selectedStatusIndex.value[0]]
|
||
whereClause += ` AND status = '${selectedStatus.value}'`
|
||
}
|
||
|
||
const result = await supa.executeAs<Incident>('eldercare_admin', `
|
||
SELECT * FROM ec_incidents
|
||
${whereClause}
|
||
ORDER BY incident_time DESC, created_at DESC
|
||
`)
|
||
incidents.value = result
|
||
}
|
||
|
||
// 加载统计数据
|
||
async function loadStats(): Promise<void> {
|
||
const result = await supa.executeAs<IncidentStats>('eldercare_admin', `
|
||
SELECT
|
||
COUNT(*) as total_incidents,
|
||
COUNT(CASE WHEN severity IN ('high', 'critical') THEN 1 END) as high_severity,
|
||
COUNT(CASE WHEN status IN ('reported', 'investigating') THEN 1 END) as pending_incidents,
|
||
COUNT(CASE WHEN status = 'resolved' THEN 1 END) as resolved_incidents
|
||
FROM ec_incidents
|
||
`)
|
||
|
||
if (result.length > 0) {
|
||
stats.value = result[0]
|
||
}
|
||
}
|
||
|
||
// 获取老人姓名
|
||
function getElderName(elderId: string): string {
|
||
return eldersMap.value.get(elderId) ?? '未知老人'
|
||
}
|
||
|
||
// 获取类型文本
|
||
function getTypeText(type: string): string {
|
||
const typeMap: Record<string, string> = {
|
||
'fall': '跌倒',
|
||
'medical': '医疗事件',
|
||
'behavior': '行为事件',
|
||
'medication': '用药事件',
|
||
'equipment': '设备故障',
|
||
'other': '其他事件'
|
||
}
|
||
return typeMap[type] ?? type
|
||
}
|
||
|
||
// 获取严重性文本
|
||
function getSeverityText(severity: string): string {
|
||
const severityMap: Record<string, string> = {
|
||
'low': '低',
|
||
'medium': '中',
|
||
'high': '高',
|
||
'critical': '紧急'
|
||
}
|
||
return severityMap[severity] ?? severity
|
||
}
|
||
|
||
// 获取状态文本
|
||
function getStatusText(status: string): string {
|
||
const statusMap: Record<string, string> = {
|
||
'reported': '已报告',
|
||
'investigating': '调查中',
|
||
'resolved': '已解决',
|
||
'closed': '已关闭'
|
||
}
|
||
return statusMap[status] ?? status
|
||
}
|
||
|
||
// 格式化日期
|
||
function formatDate(dateStr: string | null): string {
|
||
if (dateStr === null || dateStr === '') return ''
|
||
const date = new Date(dateStr)
|
||
const year = date.getFullYear()
|
||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||
const day = date.getDate().toString().padStart(2, '0')
|
||
return `${year}-${month}-${day}`
|
||
}
|
||
|
||
// 筛选事件
|
||
function onElderChange(e: any): void {
|
||
selectedElderIndex.value = e.detail.value
|
||
}
|
||
|
||
function onTypeChange(e: any): void {
|
||
selectedTypeIndex.value = e.detail.value
|
||
}
|
||
|
||
function onSeverityChange(e: any): void {
|
||
selectedSeverityIndex.value = e.detail.value
|
||
}
|
||
|
||
function onStatusChange(e: any): void {
|
||
selectedStatusIndex.value = e.detail.value
|
||
}
|
||
|
||
// 搜索事件
|
||
function searchIncidents(): void {
|
||
loadIncidents()
|
||
loadStats()
|
||
}
|
||
|
||
// 查看事件详情
|
||
function viewIncidentDetail(incident: Incident): void {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/incident/detail?id=${incident.id}`
|
||
})
|
||
}
|
||
|
||
// 编辑事件
|
||
function editIncident(incident: Incident): void {
|
||
isEditMode.value = true
|
||
currentIncidentId.value = incident.id
|
||
|
||
// 填充表单数据
|
||
const elderIndex = elderOptions.value.findIndex(elder => elder.id === incident.elder_id)
|
||
const typeIndex = incidentTypes.findIndex(type => type.value === incident.incident_type)
|
||
const severityIndex = incidentSeverities.findIndex(severity => severity.value === incident.severity)
|
||
const incidentDateTime = incident.incident_time ? new Date(incident.incident_time) : new Date()
|
||
|
||
formData.value = {
|
||
elderIndex: [elderIndex > 0 ? elderIndex : 0],
|
||
title: incident.title,
|
||
typeIndex: [typeIndex > 0 ? typeIndex : 0],
|
||
severityIndex: [severityIndex > 0 ? severityIndex : 0],
|
||
location: incident.location ?? '',
|
||
incident_date: formatDate(incidentDateTime.toISOString()),
|
||
incident_time: incidentDateTime.getHours().toString().padStart(2, '0') + ':' + incidentDateTime.getMinutes().toString().padStart(2, '0'),
|
||
description: incident.description ?? '',
|
||
actions_taken: incident.actions_taken ?? '',
|
||
witnesses: incident.witnesses?.join(', ') ?? '',
|
||
follow_up_required: incident.follow_up_required
|
||
}
|
||
|
||
showIncidentModal.value = true
|
||
}
|
||
|
||
// 解决事件
|
||
async function resolveIncident(incident: Incident): Promise<void> {
|
||
uni.showModal({
|
||
title: '确认解决',
|
||
content: '确定标记此事件为已解决吗?',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
await supa.executeAs('eldercare_admin', `
|
||
UPDATE ec_incidents
|
||
SET status = 'resolved', resolved_at = NOW(), updated_at = NOW()
|
||
WHERE id = '${incident.id}'
|
||
`)
|
||
|
||
uni.showToast({
|
||
title: '解决成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
loadIncidents()
|
||
loadStats()
|
||
} catch (error) {
|
||
console.error('解决事件失败:', error)
|
||
uni.showToast({
|
||
title: '操作失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 生成报告
|
||
function generateReport(incident: Incident): void {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/incident/report?id=${incident.id}`
|
||
})
|
||
}
|
||
|
||
// 显示添加事件弹窗
|
||
function showAddIncident(): void {
|
||
isEditMode.value = false
|
||
currentIncidentId.value = null
|
||
|
||
// 重置表单
|
||
const now = new Date()
|
||
const today = formatDate(now.toISOString())
|
||
const currentTime = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0')
|
||
|
||
formData.value = {
|
||
elderIndex: [0],
|
||
title: '',
|
||
typeIndex: [0],
|
||
severityIndex: [0],
|
||
location: '',
|
||
incident_date: today,
|
||
incident_time: currentTime,
|
||
description: '',
|
||
actions_taken: '',
|
||
witnesses: '',
|
||
follow_up_required: false
|
||
}
|
||
|
||
showIncidentModal.value = true
|
||
}
|
||
|
||
// 关闭弹窗
|
||
function closeIncidentModal(): void {
|
||
showIncidentModal.value = false
|
||
}
|
||
|
||
// 表单事件
|
||
function onFormElderChange(e: any): void {
|
||
formData.value.elderIndex = e.detail.value
|
||
}
|
||
|
||
function onFormTypeChange(e: any): void {
|
||
formData.value.typeIndex = e.detail.value
|
||
}
|
||
|
||
function onFormSeverityChange(e: any): void {
|
||
formData.value.severityIndex = e.detail.value
|
||
}
|
||
|
||
function onIncidentDateChange(date: string): void {
|
||
formData.value.incident_date = date
|
||
}
|
||
|
||
function onIncidentTimeChange(time: string): void {
|
||
formData.value.incident_time = time
|
||
}
|
||
|
||
// 保存事件
|
||
async function saveIncident(): Promise<void> {
|
||
// 验证表单
|
||
if (formData.value.elderIndex[0] === 0) {
|
||
uni.showToast({
|
||
title: '请选择老人',
|
||
icon: 'error'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (formData.value.title.trim() === '') {
|
||
uni.showToast({
|
||
title: '请输入事件标题',
|
||
icon: 'error'
|
||
})
|
||
return
|
||
}
|
||
|
||
try {
|
||
const selectedElder = elderOptions.value[formData.value.elderIndex[0]]
|
||
const selectedType = incidentTypes[formData.value.typeIndex[0]]
|
||
const selectedSeverity = incidentSeverities[formData.value.severityIndex[0]]
|
||
const incidentDateTime = `${formData.value.incident_date} ${formData.value.incident_time}:00`
|
||
const witnessesArray = formData.value.witnesses.split(',').map(w => w.trim()).filter(w => w !== '')
|
||
|
||
if (isEditMode.value && currentIncidentId.value !== null) {
|
||
// 更新事件
|
||
await supa.executeAs('eldercare_admin', `
|
||
UPDATE ec_incidents SET
|
||
elder_id = '${selectedElder.id}',
|
||
title = '${formData.value.title}',
|
||
incident_type = '${selectedType.value}',
|
||
severity = '${selectedSeverity.value}',
|
||
location = '${formData.value.location}',
|
||
incident_time = '${incidentDateTime}',
|
||
description = '${formData.value.description}',
|
||
actions_taken = '${formData.value.actions_taken}',
|
||
witnesses = '${JSON.stringify(witnessesArray)}',
|
||
follow_up_required = ${formData.value.follow_up_required},
|
||
updated_at = NOW()
|
||
WHERE id = '${currentIncidentId.value}'
|
||
`)
|
||
} else {
|
||
// 新增事件
|
||
await supa.executeAs('eldercare_admin', `
|
||
INSERT INTO ec_incidents (
|
||
elder_id, title, incident_type, severity, location,
|
||
incident_time, description, actions_taken, witnesses,
|
||
follow_up_required, status
|
||
) VALUES (
|
||
'${selectedElder.id}',
|
||
'${formData.value.title}',
|
||
'${selectedType.value}',
|
||
'${selectedSeverity.value}',
|
||
'${formData.value.location}',
|
||
'${incidentDateTime}',
|
||
'${formData.value.description}',
|
||
'${formData.value.actions_taken}',
|
||
'${JSON.stringify(witnessesArray)}',
|
||
${formData.value.follow_up_required},
|
||
'reported'
|
||
)
|
||
`)
|
||
}
|
||
|
||
uni.showToast({
|
||
title: '保存成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
closeIncidentModal()
|
||
loadIncidents()
|
||
loadStats()
|
||
} catch (error) {
|
||
console.error('保存失败:', error)
|
||
uni.showToast({
|
||
title: '保存失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.incident-management {
|
||
padding: 20px;
|
||
background-color: #f5f5f5;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.title {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.add-btn {
|
||
background-color: #2196f3;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 10px 20px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.stats-section {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.stat-card {
|
||
flex: 1;
|
||
min-width: 200px;
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin: 5px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.stat-icon {
|
||
font-size: 32px;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.stat-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.stat-number {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
display: block;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
.filter-section {
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.filter-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-right: 20px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.filter-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.picker {
|
||
width: 120px;
|
||
height: 40px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.picker-item {
|
||
padding: 10px;
|
||
text-align: center;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.search-btn {
|
||
background-color: #4caf50;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 10px 20px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.incidents-list {
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
}
|
||
|
||
.incident-item {
|
||
padding: 15px 0;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.incident-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.incident-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.severity-badge {
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.severity-text {
|
||
color: white;
|
||
}
|
||
|
||
.severity-low {
|
||
background-color: #4caf50;
|
||
}
|
||
|
||
.severity-medium {
|
||
background-color: #ff9800;
|
||
}
|
||
|
||
.severity-high {
|
||
background-color: #f44336;
|
||
}
|
||
|
||
.severity-critical {
|
||
background-color: #d32f2f;
|
||
}
|
||
|
||
.incident-info, .incident-time, .incident-description, .incident-status {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.elder-name, .incident-type, .incident-location, .time-text, .description-text {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.description-text {
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.incident-status {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.status-badge {
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.status-text {
|
||
color: white;
|
||
}
|
||
|
||
.status-reported {
|
||
background-color: #ff9800;
|
||
}
|
||
|
||
.status-investigating {
|
||
background-color: #2196f3;
|
||
}
|
||
|
||
.status-resolved {
|
||
background-color: #4caf50;
|
||
}
|
||
|
||
.status-closed {
|
||
background-color: #9e9e9e;
|
||
}
|
||
|
||
.follow-up {
|
||
font-size: 12px;
|
||
color: #f44336;
|
||
background-color: #ffebee;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.incident-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.action-btn {
|
||
border: none;
|
||
border-radius: 4px;
|
||
padding: 6px 12px;
|
||
font-size: 12px;
|
||
margin-right: 10px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.edit-btn {
|
||
background-color: #ff9800;
|
||
color: white;
|
||
}
|
||
|
||
.resolve-btn {
|
||
background-color: #4caf50;
|
||
color: white;
|
||
}
|
||
|
||
.report-btn {
|
||
background-color: #2196f3;
|
||
color: white;
|
||
}
|
||
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0,0,0,0.5);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.modal-content {
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
width: 90%;
|
||
max-width: 600px;
|
||
max-height: 80%;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.close-btn {
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
color: #999;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 20px;
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.form-label {
|
||
font-size: 14px;
|
||
color: #333;
|
||
margin-bottom: 8px;
|
||
display: block;
|
||
}
|
||
|
||
.form-input {
|
||
width: 100%;
|
||
height: 40px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 6px;
|
||
padding: 0 12px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-picker {
|
||
width: 100%;
|
||
height: 40px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.form-textarea {
|
||
width: 100%;
|
||
height: 80px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 6px;
|
||
padding: 12px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.datetime-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.date-input, .time-input {
|
||
flex: 1;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.time-input {
|
||
margin-right: 0;
|
||
}
|
||
|
||
.checkbox-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.checkbox-label {
|
||
margin-left: 8px;
|
||
font-size: 14px;
|
||
color: #333;
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: flex-end;
|
||
padding: 20px;
|
||
border-top: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.cancel-btn, .save-btn {
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 10px 20px;
|
||
font-size: 14px;
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.cancel-btn {
|
||
background-color: #f5f5f5;
|
||
color: #666;
|
||
}
|
||
|
||
.save-btn {
|
||
background-color: #2196f3;
|
||
color: white;
|
||
}
|
||
|
||
/* 小屏幕适配 */
|
||
@media (max-width: 768px) {
|
||
.incident-management {
|
||
padding: 15px;
|
||
}
|
||
|
||
.stats-section {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.stat-card {
|
||
margin: 5px 0;
|
||
min-width: auto;
|
||
}
|
||
|
||
.filter-section {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.filter-item {
|
||
margin-right: 0;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.picker {
|
||
width: 150px;
|
||
}
|
||
|
||
.modal-content {
|
||
width: 95%;
|
||
}
|
||
|
||
.datetime-row {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.date-input, .time-input {
|
||
margin-right: 0;
|
||
margin-bottom: 10px;
|
||
}
|
||
}
|
||
</style>
|