Files
hospital-rescue-app-v2/pages/event/index.uvue
T
2026-09-24 16:25:22 +08:00

275 lines
8.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="hr-page" style="background-color:#F3F4F6;position:relative">
<hr-statusbar bg="#FFFFFF"></hr-statusbar>
<!-- ============ 执行状态 Tab(进行中 / 历史) ============ -->
<x-tabs :list="tabs" :model-value="activeId" item-width="50%" height="88rpx" font-size="28rpx"
active-font-size="28rpx" title-color="#6B7280" active-title-color="#2563EB" line-color="#2563EB"
title-padding="0" :is-item-center="true" @change="onTabChange"></x-tabs>
<view class="hr-flow">
<scroll-view class="hr-scroll" :scroll-y="true" :show-scrollbar="false">
<view class="hr-body">
<view v-for="(e, i) in shownList" :key="e.id" :class="cardClass(e)" @click="onCardClick(e)">
<view class="ev-top">
<view class="evt-dot" :style="dotStyle(e)"></view>
<text class="ev-name">{{ e.name }}</text>
<hr-pc :level="e.priority"></hr-pc>
</view>
<text class="ev-meta">{{ e.kind }}</text>
<text class="ev-meta2">现场:{{ e.place }} · {{ e.distance }}</text>
<view class="ev-foot">
<x-avatar-group :list="avatarsOf(e)" size="44rpx" round="14rpx" gutter="12rpx"
:max-count="5" :random-bg-color="true" font-size="22rpx"></x-avatar-group>
<view class="hr-flex1"></view>
<view class="hr-row">
<text class="ev-go">{{ e.stage == 'history' ? '已完成' : '详情' }}</text>
<x-icon name="arrow-right-s-line" color="#2563EB" font-size="26rpx"></x-icon>
</view>
</view>
</view>
<!-- 进行中说明 -->
<text v-if="tab == 0" class="ev-note">仅显示与我相关的派单</text>
<text v-if="tab == 0" class="ev-note2">时间与处理状态由后台同步,APP 端不展示</text>
<hr-empty v-if="shownList.length == 0" icon="alarm-warning-line" title="暂无事件"
sub="分配给你的派单会出现在「进行中」,处置完成后归档到「历史」"></hr-empty>
<view style="height:28rpx"></view>
</view>
</scroll-view>
<!-- 加载中覆盖层(组件库 xLoading -->
<view v-if="loading" class="hr-loading-mask">
<x-loading label="加载中…" :icon-size="'44rpx'" :text-size="'26rpx'" :vertical="true" color="#2563EB"
text-color="#6B7280"></x-loading>
</view>
</view>
<hr-tabbar :active="1"></hr-tabbar>
</view>
</template>
<script lang="uts" setup>
import { ref, computed } from 'vue'
import { eventList, eventDetail } from '@/mock/index.uts'
import { HrEventItem, HrEventMember } from '@/mock/types.uts'
import { navTo } from '@/utils/nav.uts'
import { mockRequest } from '@/utils/request.uts'
import { TABS_ITEM_INFO, TABS_ITEM } from '@/uni_modules/tmx-ui/interface.uts'
import { markEventsRead } from '@/utils/unread.uts'
/**
* s07 / s42 事件中心(执行态)
* 用「执行状态」Tab 切换:进行中 / 历史(组件库 xTabs)。
* ⚠ 原「待接收」Tab 及其全屏推送接收流程(60 秒倒计时 / 立即接收 / 稍后处理)已移除,
* 页面只服务进行中与历史两类事件。
*/
const stageIds : string[] = ['0', '1']
const activeId = ref('0')
const loading = ref(true)
/*
* 紧急事件红点「呼吸」光环
* ⚠ App 端(uvue)不支持 @keyframes / animation(编译期直接报错),
* 改为定时器逐帧推进 pulseF,再由 dotStyle() 下发 box-shadow。
* 周期 1.7s = 25 帧 × 68ms;第 0~19 帧扩散(19/25 ≈ 设计稿 75%),第 20~24 帧静默。
* 触发条件只看数据的 urgent 字段(与 stage 无关)。
*/
const PULSE_FRAMES = 25
const PULSE_RAMP = 19
const pulseF = ref(0)
let pulseTimer : number = 0
function startPulse() : void {
if (pulseTimer != 0) { return }
pulseTimer = setInterval(() => {
const f = pulseF.value + 1
pulseF.value = f >= PULSE_FRAMES ? 0 : f
}, 68)
}
function stopPulse() : void {
if (pulseTimer != 0) {
clearInterval(pulseTimer)
pulseTimer = 0
}
}
/** 红点样式:底色取数据里的 dotColor;紧急事件额外叠加扩散光环 */
function dotStyle(e : HrEventItem) : string {
const base = 'background-color:' + e.dotColor + ';'
if (!e.urgent) { return base }
const i = pulseF.value
// 扩散结束后的静默帧:半径与透明度都归零(设计稿 75% → 100%)
if (i > PULSE_RAMP) { return base + 'box-shadow:0 0 0 0rpx rgba(220, 38, 38, 0);' }
const k = i / PULSE_RAMP
const r = Math.round(k * 16)
const a = Math.round(55 * (1 - k))
return base + 'box-shadow:0 0 0 ' + r.toString() + 'rpx rgba(220, 38, 38, 0.' + a.toString() + ');'
}
onLoad(() => {
// 模拟 1s 请求,期间展示 xSkeleton 骨架屏
mockRequest(() => {
loading.value = false
})
startPulse()
})
// 进入事件列表即视为已查看 → TabBar 事件角标隐藏;同时恢复红点呼吸
onPageShow(() => {
markEventsRead()
startPulse()
})
// 页面不可见时停掉呼吸定时器,避免后台空转
onPageHide(() => {
stopPulse()
})
/** 当前 tab 下标(由 xTabs 的字符串 id 换算) */
const tab = computed(() : number => {
const i = stageIds.indexOf(activeId.value)
return i < 0 ? 0 : i
})
/** 顶部执行状态切换:只展示标题,不带数量角标(角标会挤压标题并干扰视线) */
const tabs = computed(() : TABS_ITEM_INFO[] => {
return [
{ title: '进行中', id: '0' } as TABS_ITEM_INFO,
{ title: '历史', id: '1' } as TABS_ITEM_INFO
]
})
const shownList = computed(() : HrEventItem[] => {
const stage = tab.value == 0 ? 'ongoing' : 'history'
const list : HrEventItem[] = []
for (let i = 0; i < eventList.length; i++) {
if (eventList[i].stage == stage) { list.push(eventList[i]) }
}
return list
})
/** xAvatarGroup 需要 string[] */
function avatarsOf(e : HrEventItem) : string[] {
const arr : string[] = []
for (let i = 0; i < e.members.length; i++) {
arr.push(e.members[i].avatar)
}
return arr
}
// ⚠ x-tabs 的 change 下发的是 TABS_ITEM(库内部把 TABS_ITEM_INFO 转成了 TABS_ITEM),
// 形参写 TABS_ITEM_INFO 会在 App 端报 Kotlin 类型不匹配
function onTabChange(item : TABS_ITEM, index : number) : void {
activeId.value = item.id == '' ? index.toString() : item.id
}
function cardClass(e : HrEventItem) : string {
return e.urgent ? 'evt-card evt-urgent' : 'evt-card'
}
function onFilter() : void {
uni.showToast({ title: '筛选:按状态 / 优先级 / 时间', icon: 'none' })
}
/** 点卡片统一进入事件详情(原「待接收 → 唤起全屏推送」分支已随待接收 Tab 一并移除) */
function onCardClick(e : HrEventItem) : void {
navTo('/pages/event/detail')
}
onUnload(() => {
stopPulse()
})
</script>
<style>
/* 顶部标题栏 / 自绘 Tab / 数量角标的样式均已移除:
导航由 hr-statusbar 承担,状态切换改用组件库 x-tabs。 */
/* ---------- 事件卡片 ---------- */
.evt-card {
background-color: #FFFFFF;
border: 2rpx solid #E5E7EB;
border-radius: 26rpx;
padding: 24rpx 26rpx 24rpx 26rpx;
margin-bottom: 18rpx;
}
.evt-urgent {
border: 2rpx solid #FECACA;
background-color: #FFFBFB;
}
.ev-top {
display: flex;
flex-direction: row;
align-items: center;
}
.evt-dot {
width: 16rpx;
height: 16rpx;
border-radius: 8rpx;
margin: 0 16rpx;
}
.ev-name {
flex: 1;
font-size: 30.2rpx;
font-weight: bold;
color: #1F2937;
margin-right: 16rpx;
}
.ev-meta {
font-size: 25.8rpx;
color: #6B7280;
margin-top: 12rpx;
}
.ev-meta2 {
font-size: 25.8rpx;
color: #6B7280;
margin-top: 2rpx;
}
.ev-foot {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 18rpx;
}
.ev-go {
font-size: 25.8rpx;
color: #2563EB;
font-weight: bold;
}
.ev-note {
text-align: center;
font-size: 23.6rpx;
color: #9CA3AF;
margin-top: 16rpx;
}
.ev-note2 {
text-align: center;
font-size: 23.6rpx;
color: #9CA3AF;
margin-top: 2rpx;
}
/* 紧急事件红点呼吸(设计稿 evtPulse)
⚠ App 端 uvue 不支持 @keyframes / animation(编译期直接报 ERROR),
已改为页面内 setInterval 推进 pulseF,再由 dotStyle() 下发 box-shadow —— 见 script 顶部。
原 P0 全屏推送的 .ev-push* 整组样式已随「待接收」流程一并移除。 */
</style>