|
|
@@ -0,0 +1,485 @@
|
|
|
+<template>
|
|
|
+ <el-dialog
|
|
|
+ v-model="visible"
|
|
|
+ title="长者预警服务"
|
|
|
+ width="900px"
|
|
|
+ class="large-screen-dialog elder-warning-service-dialog"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ @close="handleClose"
|
|
|
+ >
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <div v-if="loading" class="loading-container">
|
|
|
+ <el-icon class="is-loading">
|
|
|
+ <Loading />
|
|
|
+ </el-icon>
|
|
|
+ <p>加载中...</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 表格内容 -->
|
|
|
+ <div v-else class="warning-service-content">
|
|
|
+ <!-- 表格 -->
|
|
|
+ <el-table
|
|
|
+ :data="warningList"
|
|
|
+ style="width: 100%; margin-bottom: 20px"
|
|
|
+ :default-sort="{ prop: 'createdTime', order: 'descending' }"
|
|
|
+ >
|
|
|
+ <el-table-column prop="name" label="长者姓名" width="120" />
|
|
|
+ <el-table-column prop="eventType" label="预警类型" width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="getEventTypeTag(row.eventType)">
|
|
|
+ {{ row.eventType }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="message" label="预警信息" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="createdTime" label="创建时间" width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ formatTime(row.createdTime) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <div class="pagination-container">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="pageNum"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ :total="total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handlePageChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import { ref, computed, watch } from 'vue'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+import { Loading } from '@element-plus/icons-vue'
|
|
|
+import fetchHttp from '@/config/axios/fetchHttp'
|
|
|
+import { getAccessToken } from '@/utils/auth'
|
|
|
+import { formatToDateTime } from '@/utils/dateUtil'
|
|
|
+
|
|
|
+// 类型定义
|
|
|
+interface WarningRecord {
|
|
|
+ name: string
|
|
|
+ eventType: string
|
|
|
+ message: string
|
|
|
+ createdTime: string
|
|
|
+}
|
|
|
+
|
|
|
+interface WarningServiceResponse {
|
|
|
+ total: number
|
|
|
+ list: WarningRecord[]
|
|
|
+}
|
|
|
+
|
|
|
+// Props 和 Emits
|
|
|
+interface Props {
|
|
|
+ modelValue: boolean
|
|
|
+ elderId?: number
|
|
|
+}
|
|
|
+
|
|
|
+const props = withDefaults(defineProps<Props>(), {
|
|
|
+ elderId: 0
|
|
|
+})
|
|
|
+
|
|
|
+const emit = defineEmits<{
|
|
|
+ 'update:modelValue': [value: boolean]
|
|
|
+}>()
|
|
|
+
|
|
|
+// 响应式数据
|
|
|
+const loading = ref(false)
|
|
|
+const warningList = ref<WarningRecord[]>([])
|
|
|
+const total = ref(0)
|
|
|
+const pageNum = ref(1)
|
|
|
+const pageSize = ref(10)
|
|
|
+
|
|
|
+const visible = computed({
|
|
|
+ get: () => props.modelValue,
|
|
|
+ set: (value: boolean) => {
|
|
|
+ emit('update:modelValue', value)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 监听 elderId 变化,自动加载数据
|
|
|
+watch(
|
|
|
+ () => props.elderId,
|
|
|
+ (newId) => {
|
|
|
+ if (newId && visible.value) {
|
|
|
+ pageNum.value = 1
|
|
|
+ fetchWarningServiceData(newId)
|
|
|
+ }
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+// 监听 visible 变化
|
|
|
+watch(
|
|
|
+ () => visible.value,
|
|
|
+ (newVisible) => {
|
|
|
+ if (newVisible && props.elderId) {
|
|
|
+ pageNum.value = 1
|
|
|
+ fetchWarningServiceData(props.elderId)
|
|
|
+ }
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+// 方法
|
|
|
+const formatTime = (time: string | number) => {
|
|
|
+ if (!time) return '未知'
|
|
|
+ return formatToDateTime(time)
|
|
|
+}
|
|
|
+
|
|
|
+const getEventTypeTag = (eventType: string) => {
|
|
|
+ if (eventType === 'SOS报警') {
|
|
|
+ return 'danger'
|
|
|
+ } else if (eventType === '健康指标异常') {
|
|
|
+ return 'warning'
|
|
|
+ }
|
|
|
+ return 'info'
|
|
|
+}
|
|
|
+
|
|
|
+// 生成模拟数据(支持分页)
|
|
|
+const generateMockServiceData = (
|
|
|
+ elderId: number,
|
|
|
+ pageNum: number,
|
|
|
+ pageSize: number
|
|
|
+): WarningServiceResponse => {
|
|
|
+ const names = ['王奶奶', '李爷爷', '张奶奶', '刘爷爷', '陈奶奶', '杨爷爷']
|
|
|
+ const eventTypes = ['SOS报警', '健康指标异常']
|
|
|
+ const healthMsgs = [
|
|
|
+ '心率过高,建议就医检查',
|
|
|
+ '血压偏高,请注意休息',
|
|
|
+ '血氧偏低,请及时关注',
|
|
|
+ '体温异常,建议复测'
|
|
|
+ ]
|
|
|
+ const sosMsgs = [
|
|
|
+ '手表发出SOS呼叫,请立即回拨',
|
|
|
+ '设备触发紧急求助,请尽快联系',
|
|
|
+ '跌倒疑似SOS,请核实情况'
|
|
|
+ ]
|
|
|
+
|
|
|
+ const total = 37 // 模拟总条数
|
|
|
+ const all: WarningRecord[] = Array.from({ length: total }).map((_, idx) => {
|
|
|
+ const type = eventTypes[Math.floor(Math.random() * eventTypes.length)]
|
|
|
+ const msg =
|
|
|
+ type === 'SOS报警'
|
|
|
+ ? sosMsgs[Math.floor(Math.random() * sosMsgs.length)]
|
|
|
+ : healthMsgs[Math.floor(Math.random() * healthMsgs.length)]
|
|
|
+ const daysAgo = Math.floor(Math.random() * 15)
|
|
|
+ const date = new Date(
|
|
|
+ Date.now() - daysAgo * 24 * 60 * 60 * 1000 - Math.floor(Math.random() * 86400000)
|
|
|
+ )
|
|
|
+ return {
|
|
|
+ name: names[elderId % names.length],
|
|
|
+ eventType: type,
|
|
|
+ message: msg,
|
|
|
+ createdTime: date.toISOString()
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const start = (pageNum - 1) * pageSize
|
|
|
+ const end = start + pageSize
|
|
|
+ return {
|
|
|
+ total,
|
|
|
+ list: all.slice(start, end)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const fetchWarningServiceData = async (elderId: number) => {
|
|
|
+ if (!elderId) return
|
|
|
+
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await fetchHttp.get(
|
|
|
+ '/api/pc/admin/getElderServer',
|
|
|
+ {
|
|
|
+ elderId: elderId,
|
|
|
+ pageNum: pageNum.value,
|
|
|
+ pageSize: pageSize.value
|
|
|
+ },
|
|
|
+ {
|
|
|
+ headers: {
|
|
|
+ Authorization: `Bearer ${getAccessToken()}`
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ if (res && Array.isArray(res.list) && res.list.length > 0) {
|
|
|
+ total.value = res.total || 0
|
|
|
+ warningList.value = res.list || []
|
|
|
+ } else {
|
|
|
+ // 接口暂无数据,使用模拟数据
|
|
|
+ const mock = generateMockServiceData(elderId, pageNum.value, pageSize.value)
|
|
|
+ total.value = mock.total
|
|
|
+ warningList.value = mock.list
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取预警服务数据失败:', error)
|
|
|
+ // 接口异常,使用模拟数据
|
|
|
+ const mock = generateMockServiceData(elderId, pageNum.value, pageSize.value)
|
|
|
+ total.value = mock.total
|
|
|
+ warningList.value = mock.list
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handlePageChange = (newPageNum: number) => {
|
|
|
+ pageNum.value = newPageNum
|
|
|
+ if (props.elderId) {
|
|
|
+ fetchWarningServiceData(props.elderId)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleSizeChange = (newPageSize: number) => {
|
|
|
+ pageSize.value = newPageSize
|
|
|
+ pageNum.value = 1
|
|
|
+ if (props.elderId) {
|
|
|
+ fetchWarningServiceData(props.elderId)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleClose = () => {
|
|
|
+ warningList.value = []
|
|
|
+ total.value = 0
|
|
|
+ pageNum.value = 1
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+$primary-color: #1a73e8;
|
|
|
+$secondary-color: #00c6ff;
|
|
|
+$accent-color: #7b61ff;
|
|
|
+$text-light: #fff;
|
|
|
+$text-gray: #8a8f98;
|
|
|
+$success-color: #26de81;
|
|
|
+$warning-color: #fd9644;
|
|
|
+$danger-color: #ff6b6b;
|
|
|
+
|
|
|
+.elder-warning-service-dialog {
|
|
|
+ :deep(.el-dialog__header) {
|
|
|
+ /* 覆盖全局 large-screen-dialog 的额外上边距,避免顶部出现空隙 */
|
|
|
+ margin-top: 0 !important;
|
|
|
+ padding: 18px !important;
|
|
|
+ background: linear-gradient(90deg, $primary-color, $accent-color) !important;
|
|
|
+ border-radius: 12px 12px 0 0 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__title) {
|
|
|
+ font-size: 18px !important;
|
|
|
+ font-weight: 600 !important;
|
|
|
+ color: white !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__body) {
|
|
|
+ padding: 24px !important;
|
|
|
+ background: transparent !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__close) {
|
|
|
+ color: white !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.loading-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 40px 20px;
|
|
|
+ text-align: center;
|
|
|
+ color: $text-gray;
|
|
|
+
|
|
|
+ .el-icon {
|
|
|
+ font-size: 32px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ color: $primary-color;
|
|
|
+
|
|
|
+ &.is-loading {
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes spin {
|
|
|
+ 0% {
|
|
|
+ transform: rotate(0deg);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.warning-service-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+// 表格样式
|
|
|
+:deep(.el-table) {
|
|
|
+ background: transparent !important;
|
|
|
+ border: 1px solid rgb(255 255 255 / 10%) !important;
|
|
|
+ border-radius: 8px !important;
|
|
|
+
|
|
|
+ .el-table__header {
|
|
|
+ background: rgb(255 255 255 / 5%) !important;
|
|
|
+
|
|
|
+ th {
|
|
|
+ background: rgb(255 255 255 / 5%) !important;
|
|
|
+ color: $text-light !important;
|
|
|
+ border-bottom: 1px solid rgb(255 255 255 / 10%) !important;
|
|
|
+ font-weight: 600 !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-table__body {
|
|
|
+ tr {
|
|
|
+ background: transparent !important;
|
|
|
+
|
|
|
+ &:hover > td {
|
|
|
+ background: rgb(26 115 232 / 10%) !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ td {
|
|
|
+ background: transparent !important; /* 解决白底行问题 */
|
|
|
+ border-bottom: 1px solid rgb(255 255 255 / 5%) !important;
|
|
|
+ color: $text-light !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-table__header {
|
|
|
+ tr {
|
|
|
+ background: transparent !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 彻底去除单元格默认白底 */
|
|
|
+ .el-table__cell {
|
|
|
+ background: transparent !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-table__row {
|
|
|
+ &.hover-row > td {
|
|
|
+ background: rgb(26 115 232 / 10%) !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 标签样式
|
|
|
+:deep(.el-tag) {
|
|
|
+ border-radius: 4px !important;
|
|
|
+
|
|
|
+ &.el-tag--danger {
|
|
|
+ background: rgb(255 107 107 / 20%) !important;
|
|
|
+ border-color: $danger-color !important;
|
|
|
+ color: $danger-color !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.el-tag--warning {
|
|
|
+ background: rgb(253 150 68 / 20%) !important;
|
|
|
+ border-color: $warning-color !important;
|
|
|
+ color: $warning-color !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.el-tag--info {
|
|
|
+ background: rgb(26 115 232 / 20%) !important;
|
|
|
+ border-color: $primary-color !important;
|
|
|
+ color: $primary-color !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 分页样式
|
|
|
+.pagination-container {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ padding-top: 20px;
|
|
|
+ border-top: 1px solid rgb(255 255 255 / 10%);
|
|
|
+
|
|
|
+ :deep(.el-pagination) {
|
|
|
+ .btn-prev,
|
|
|
+ .btn-next,
|
|
|
+ .pager li {
|
|
|
+ background: rgb(255 255 255 / 5%) !important;
|
|
|
+ color: $text-light !important;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: $primary-color !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ color: $primary-color !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-pagination__total {
|
|
|
+ color: #fff !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-pager li {
|
|
|
+ color: #fff !important;
|
|
|
+ background: rgb(255 255 255 / 5%) !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-pager li.is-active {
|
|
|
+ color: $primary-color !important;
|
|
|
+ font-weight: 600 !important;
|
|
|
+ background: rgb(255 255 255 / 5%) !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-pagination__jump {
|
|
|
+ color: $text-light !important;
|
|
|
+
|
|
|
+ .el-input__wrapper {
|
|
|
+ padding: 0 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ input {
|
|
|
+ background: rgb(255 255 255 / 5%) !important;
|
|
|
+ // border: 1px solid rgb(255 255 255 / 10%) !important;
|
|
|
+ color: $text-light !important;
|
|
|
+
|
|
|
+ &:focus {
|
|
|
+ border-color: $primary-color !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-select {
|
|
|
+ .el-select__selected-item {
|
|
|
+ color: #fff !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-select__wrapper {
|
|
|
+ background: rgb(255 255 255 / 5%) !important;
|
|
|
+ }
|
|
|
+ :deep(.el-input__wrapper) {
|
|
|
+ background: rgb(255 255 255 / 5%) !important;
|
|
|
+ border: 1px solid rgb(255 255 255 / 10%) !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input__inner) {
|
|
|
+ color: $text-light !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 空状态
|
|
|
+:deep(.el-empty) {
|
|
|
+ --el-empty-padding: 40px 0;
|
|
|
+
|
|
|
+ .el-empty__description {
|
|
|
+ color: $text-gray !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|