1
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
<template>
|
||||
<hr-page page-bg="#F3F4F6" status-bg="#FFFFFF" nav-title="事件详情" :xloading="true" @right-click="onMore">
|
||||
<template v-slot:right>
|
||||
<x-icon name="more-fill" color="#6B7280" font-size="18"></x-icon>
|
||||
</template>
|
||||
|
||||
<view class="hr-body">
|
||||
|
||||
<!-- ============ 事件 hero ============ -->
|
||||
<view class="evt-head">
|
||||
<view class="evt-head-c1"></view>
|
||||
<view class="evt-head-c2"></view>
|
||||
<text class="evt-head-t">{{ detail.name }}</text>
|
||||
<text class="evt-head-s">{{ detail.sub }}</text>
|
||||
|
||||
<view class="eh-team">
|
||||
<text class="eh-team-k">{{ detail.teamKey }}</text>
|
||||
<view class="eh-team-line"></view>
|
||||
<text class="eh-team-v">{{ detail.teamValue }}</text>
|
||||
</view>
|
||||
|
||||
<view class="evt-tags">
|
||||
<view v-for="(tg, i) in detail.tags" :key="i" class="evt-tag">
|
||||
<text class="evt-tag-t">{{ tg }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="evt-kpi">
|
||||
<view class="evt-kpi-cell">
|
||||
<text class="evt-kpi-b">{{ detail.victimTotal }}</text>
|
||||
<text class="evt-kpi-s">伤员总数</text>
|
||||
</view>
|
||||
<view class="evt-kpi-sep"></view>
|
||||
<view class="evt-kpi-cell">
|
||||
<text class="evt-kpi-b">{{ detail.transferred }}</text>
|
||||
<text class="evt-kpi-s">已转运</text>
|
||||
</view>
|
||||
<view class="evt-kpi-sep"></view>
|
||||
<view class="evt-kpi-cell">
|
||||
<text class="evt-kpi-b">{{ detail.pending }}</text>
|
||||
<text class="evt-kpi-s">待处置</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ============ 4 色分级计数条(常驻) ============ -->
|
||||
<view style="margin-top:10px">
|
||||
<hr-wband :red="detail.redCount" :amber="detail.amberCount" :green="detail.greenCount"
|
||||
:black="detail.blackCount"></hr-wband>
|
||||
</view>
|
||||
|
||||
<!-- ============ 工作台 ============ -->
|
||||
<hr-sec title="工作台" more="4 个入口 ›"></hr-sec>
|
||||
<view class="ws-grid">
|
||||
<view v-for="(w, i) in works" :key="i" class="ws-item"
|
||||
:class="i == works.length - 1 ? 'ws-item-last' : ''" @click="onWorkClick(i)">
|
||||
<view class="ws-ic" :style="{ backgroundColor: w.bg }">
|
||||
<x-icon :name="w.icon" :color="w.color" font-size="36rpx"></x-icon>
|
||||
</view>
|
||||
<text class="ws-t">{{ w.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ============ 救援团队 ============ -->
|
||||
<hr-sec title="救援团队" more="5 人"></hr-sec>
|
||||
<hr-card>
|
||||
<x-avatar-group :list="teamAvatars" size="88rpx" round="28rpx" gutter="18rpx" :max-count="5"
|
||||
:show-count="false" :flat="true" :random-bg-color="true" font-size="30rpx"></x-avatar-group>
|
||||
<text class="tm-line">组长 张明远 · 检伤 李承志 · 转运 王志远</text>
|
||||
</hr-card>
|
||||
|
||||
<!-- ============ 事件时间线 ============ -->
|
||||
<hr-sec title="事件时间线"></hr-sec>
|
||||
<hr-card>
|
||||
<hr-timeline :nodes="detail.timeline"></hr-timeline>
|
||||
</hr-card>
|
||||
|
||||
<view style="height:14px"></view>
|
||||
</view>
|
||||
</hr-page>
|
||||
</template>
|
||||
|
||||
<script lang="uts" setup>
|
||||
import { eventDetail } from '@/mock/index.uts'
|
||||
import { navTo } from '@/utils/nav.uts'
|
||||
|
||||
/**
|
||||
* s34 事件详情(执行态工作台)
|
||||
* 工作台由 8 入口精简为 4 入口:检伤建档 / 伤员查询 / 批量处置 / 批量转运。
|
||||
* 4 色分级计数条常驻 hero 下方。救援端不开放事件上报入口(救援员是接单方)。
|
||||
*/
|
||||
type WorkItem = { icon : string, name : string, bg : string, color : string, url : string }
|
||||
|
||||
const detail = eventDetail
|
||||
|
||||
/** xAvatarGroup 需要 string[] */
|
||||
const teamAvatars : string[] = ['张', '李', '王', '陈', '赵']
|
||||
|
||||
const works : WorkItem[] = [
|
||||
{ icon: 'heart-pulse-line', name: '检伤建档', bg: '#FEF2F2', color: '#DC2626', url: '/pages/triage/create' } as WorkItem,
|
||||
{ icon: 'file-list-3-line', name: '伤员查询', bg: '#EFF6FF', color: '#2563EB', url: '/pages/triage/victims' } as WorkItem,
|
||||
{ icon: 'clipboard-line', name: '批量处置', bg: '#ECFDF5', color: '#059669', url: '/pages/triage/batch-treat' } as WorkItem,
|
||||
{ icon: 'arrow-left-right-line', name: '批量转运', bg: '#FFFBEB', color: '#D97706', url: '/pages/triage/batch-transfer' } as WorkItem
|
||||
]
|
||||
|
||||
function onWorkClick(i : number) : void {
|
||||
navTo(works[i].url)
|
||||
}
|
||||
|
||||
function onMore() : void {
|
||||
uni.showToast({ title: '事件归档 / 分享 / 报错', icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ---------- hero ---------- */
|
||||
.evt-head {
|
||||
background-image: linear-gradient(to bottom right, #1E3A8A, #2563EB);
|
||||
border-radius: 32rpx;
|
||||
padding: 28rpx 28rpx 28rpx 28rpx;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.evt-head-c1 {
|
||||
position: absolute;
|
||||
right: -68rpx;
|
||||
top: -88rpx;
|
||||
width: 252rpx;
|
||||
height: 252rpx;
|
||||
border-radius: 126rpx;
|
||||
background-color: rgba(255, 255, 255, 0.10);
|
||||
}
|
||||
|
||||
.evt-head-c2 {
|
||||
position: absolute;
|
||||
right: 52rpx;
|
||||
bottom: -96rpx;
|
||||
width: 156rpx;
|
||||
height: 156rpx;
|
||||
border-radius: 78rpx;
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
|
||||
.evt-head-t {
|
||||
font-size: 34.8rpx;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.evt-head-s {
|
||||
font-size: 25.8rpx;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.eh-team {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-top: 20rpx;
|
||||
background-color: rgba(255, 255, 255, 0.14);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: 20rpx;
|
||||
padding: 18rpx 22rpx 18rpx 22rpx;
|
||||
}
|
||||
|
||||
.eh-team-k {
|
||||
font-size: 22.4rpx;
|
||||
font-weight: bold;
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.eh-team-line {
|
||||
width: 2rpx;
|
||||
height: 24rpx;
|
||||
background-color: rgba(255, 255, 255, 0.28);
|
||||
margin-left: 18rpx;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
|
||||
.eh-team-v {
|
||||
flex: 1;
|
||||
font-size: 26.8rpx;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.evt-tags {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.evt-tag {
|
||||
background-color: rgba(255, 255, 255, 0.18);
|
||||
border-radius: 14rpx;
|
||||
padding: 6rpx 18rpx 6rpx 18rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.evt-tag-t {
|
||||
font-size: 23.6rpx;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.evt-kpi {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.evt-kpi-cell {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.evt-kpi-b {
|
||||
font-size: 42.6rpx;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.evt-kpi-s {
|
||||
font-size: 22.4rpx;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.evt-kpi-sep {
|
||||
width: 2rpx;
|
||||
height: 60rpx;
|
||||
background-color: rgba(255, 255, 255, 0.22);
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
/* ---------- 工作台 ---------- */
|
||||
.ws-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.ws-item {
|
||||
flex: 1;
|
||||
background-color: #FFFFFF;
|
||||
border: 2rpx solid #E5E7EB;
|
||||
border-radius: 26rpx;
|
||||
padding: 28rpx 8rpx 28rpx 8rpx;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.ws-item-last {
|
||||
margin-right: 0rpx;
|
||||
}
|
||||
|
||||
.ws-ic {
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.ws-ic-t {
|
||||
font-size: 38rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ws-t {
|
||||
font-size: 25.8rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
/* ---------- 团队 ---------- */
|
||||
.tm-avt {
|
||||
margin-right: 18rpx;
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.tm-line {
|
||||
font-size: 25.8rpx;
|
||||
color: #6B7280;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,275 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user