Files
medical-mall/pages/mall/delivery/messages/index.uvue

949 lines
22 KiB
Plaintext

<template>
<ServicePageScaffold title="消息中心" fallback-url="/pages/mall/delivery/home/index" :hide-header="true">
<view class="delivery-messages-page">
<view class="delivery-messages-hero">
<view class="delivery-messages-hero-top">
<view class="delivery-messages-hero-main">
<text class="delivery-messages-hero-title">业务消息</text>
<text class="delivery-messages-hero-subtitle">{{ getCurrentTabLabel() }} · {{ getCurrentTabSubtitle() }}</text>
</view>
<view class="delivery-messages-hero-badge">
<text class="delivery-messages-hero-badge-text">{{ formatTabCount(getTabCount(currentTab)) }}</text>
</view>
</view>
<view class="delivery-messages-hero-tip-box">
<text class="delivery-messages-hero-tip">新工单、改派、异常、验收与结算消息会在这里汇总。</text>
</view>
</view>
<view class="delivery-messages-card delivery-messages-filter-card">
<view class="delivery-messages-card-header">
<view>
<text class="delivery-messages-card-title">消息分类</text>
<text class="delivery-messages-card-subtitle">横向滑动切换消息分类,当前面板会同步展示对应消息</text>
</view>
</view>
<scroll-view
class="status-scroll"
direction="horizontal"
:show-scrollbar="false"
:scroll-into-view="activeTabViewId"
scroll-with-animation="true"
>
<view class="status-tabs-row">
<view
v-for="item in tabs"
:key="item.value"
:id="'status-tab-' + item.value"
class="status-tab-item"
:class="getTabItemClass(item.value)"
@click="switchTab(item.value)"
>
<view class="status-tab-content">
<text class="status-tab-text" :class="getTabTextClass(item.value)">{{ item.label }}</text>
<text class="status-tab-count" :class="getTabCountClass(item.value)">{{ formatTabCount(getTabCount(item.value)) }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
<view v-if="messages.length > 0" class="delivery-messages-card delivery-messages-summary-card">
<view class="delivery-messages-card-header">
<view>
<text class="delivery-messages-card-title">消息概览</text>
<text class="delivery-messages-card-subtitle">关注未读、今日更新与全部消息总量</text>
</view>
</view>
<view class="message-summary">
<view class="summary-card summary-card-accent">
<text class="summary-value">{{ messageStats.unread }}</text>
<text class="summary-label">未读消息</text>
</view>
<view class="summary-card">
<text class="summary-value summary-value-dark">{{ messageStats.today }}</text>
<text class="summary-label">今日更新</text>
</view>
<view class="summary-card summary-card-last">
<text class="summary-value summary-value-dark">{{ messageStats.all }}</text>
<text class="summary-label">全部消息</text>
</view>
</view>
</view>
<view class="delivery-messages-card delivery-messages-list-card">
<view class="delivery-messages-card-header">
<view>
<text class="delivery-messages-card-title">消息列表</text>
<text class="delivery-messages-card-subtitle">突出消息级别、业务类型、未读状态与关联工单</text>
</view>
</view>
<view v-if="filteredMessages.length == 0" class="empty-box"><text class="empty-text">{{ getEmptyText() }}</text></view>
<view
v-for="item in filteredMessages"
:key="item.id"
class="message-card"
:class="getMessageCardClass(item.title, item.type, item.read)"
@click="openMessage(item.orderId)"
>
<view class="message-icon" :class="getMessageLevelClass(item.title, item.type)">
<text class="message-icon-text" :class="getMessageTextLevelClass(item.title, item.type)">{{ getMessageIconText(item.title, item.type) }}</text>
</view>
<view class="message-main">
<view class="message-title-row">
<text class="message-title" :class="getMessageTitleClass(item.title, item.type, item.read)">{{ item.title }}</text>
<text v-if="!item.read" class="message-badge">未读</text>
<text v-else class="message-read-text">已读</text>
</view>
<text class="message-biz-line" :class="getMessageTextLevelClass(item.title, item.type)">{{ getMessageBizLine(item.title, item.type, item.orderId) }}</text>
<text class="message-content">{{ item.content }}</text>
<text class="message-meta">{{ item.createdAt }}</text>
</view>
</view>
</view>
<view class="delivery-messages-safe"></view>
</view>
</ServicePageScaffold>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import ServicePageScaffold from '@/components/homeService/ServicePageScaffold.uvue'
import type { DeliveryMessageType } from '@/types/delivery.uts'
import { getDeliveryMessages } from '@/services/deliveryService.uts'
import { requireDeliveryAuth } from '@/utils/deliveryAuth.uts'
type MessageStatsType = {
all: number
unread: number
dispatch: number
exception: number
result: number
notice: number
today: number
}
const tabs = [
{ label: '全部', value: 'all' },
{ label: '未读', value: 'unread' },
{ label: '调度', value: 'dispatch' },
{ label: '异常', value: 'exception' },
{ label: '结果', value: 'result' },
{ label: '通知', value: 'notice' }
]
const currentTab = ref('all')
const activeTabViewId = ref('status-tab-all')
const messages = ref([] as Array<DeliveryMessageType>)
const filteredMessages = ref([] as Array<DeliveryMessageType>)
const messageStats = ref(createEmptyMessageStats())
function getCurrentTabLabel(): string {
for (let i = 0; i < tabs.length; i++) {
if (tabs[i].value == currentTab.value) {
return tabs[i].label
}
}
return '全部'
}
function getCurrentTabSubtitle(): string {
if (currentTab.value == 'unread') {
return '优先处理未读提醒,避免遗漏服务动作'
}
if (currentTab.value == 'dispatch') {
return '关注派单、改派、出发与签到类调度消息'
}
if (currentTab.value == 'exception') {
return '优先处理异常与风险反馈,保障服务连续性'
}
if (currentTab.value == 'result') {
return '查看验收、提交与结算结果类消息'
}
if (currentTab.value == 'notice') {
return '查看系统公告、平台通知与通用提醒'
}
return '集中查看全部业务消息与处理进展'
}
function createEmptyMessageStats(): MessageStatsType {
return {
all: 0,
unread: 0,
dispatch: 0,
exception: 0,
result: 0,
notice: 0,
today: 0
} as MessageStatsType
}
async function loadData() {
const authResult = await requireDeliveryAuth({ redirectOnFail: true, toastOnFail: true })
if (!authResult.ok) {
messages.value = [] as Array<DeliveryMessageType>
filteredMessages.value = [] as Array<DeliveryMessageType>
refreshSummary([] as Array<DeliveryMessageType>)
return
}
messages.value = await getDeliveryMessages()
refreshSummary(messages.value)
applyFilter()
}
function isValidTab(tab: string): boolean {
for (let i = 0; i < tabs.length; i++) {
if (tabs[i].value == tab) {
return true
}
}
return false
}
function syncActiveTabViewId(): void {
activeTabViewId.value = 'status-tab-' + currentTab.value
}
function applyTab(tab: string): void {
if (tab == '' || !isValidTab(tab)) {
currentTab.value = 'all'
} else {
currentTab.value = tab
}
syncActiveTabViewId()
applyFilter()
}
function switchTab(tab: string) {
if (currentTab.value == tab) {
return
}
applyTab(tab)
}
function matchesTabMessage(tab: string, item: DeliveryMessageType): boolean {
if (tab == 'all') {
return true
}
if (tab == 'unread') {
return !item.read
}
if (tab == 'dispatch') {
return item.type == 'order' || item.type == 'dispatch' || item.type == 'reminder' || item.type == 'checkin'
}
if (tab == 'exception') {
return item.type == 'exception'
}
if (tab == 'result') {
return item.type == 'result' || item.type == 'settlement'
}
if (tab == 'notice') {
return item.type == 'notice'
}
return true
}
function applyFilter(): void {
const nextList = [] as Array<DeliveryMessageType>
for (let i = 0; i < messages.value.length; i++) {
const item = messages.value[i]
if (matchesTabMessage(currentTab.value, item)) {
nextList.push(item)
}
}
filteredMessages.value = nextList
}
function getTabCount(tab: string): number {
if (tab == 'unread') {
return messageStats.value.unread
}
if (tab == 'dispatch') {
return messageStats.value.dispatch
}
if (tab == 'exception') {
return messageStats.value.exception
}
if (tab == 'result') {
return messageStats.value.result
}
if (tab == 'notice') {
return messageStats.value.notice
}
return messageStats.value.all
}
function formatTabCount(count: number): string {
if (count > 99) {
return '99+'
}
return '' + count
}
function getTabLevel(tab: string): string {
if (tab == 'exception') {
return 'danger'
}
if (tab == 'dispatch' || tab == 'unread') {
return 'warning'
}
if (tab == 'result') {
return 'success'
}
if (tab == 'notice') {
return 'neutral'
}
return 'default'
}
function getTabItemClass(tab: string): string {
if (currentTab.value != tab) {
return ''
}
const level = getTabLevel(tab)
if (level == 'danger') {
return 'status-tab-active status-tab-active-danger'
}
if (level == 'warning') {
return 'status-tab-active status-tab-active-warning'
}
if (level == 'success') {
return 'status-tab-active status-tab-active-success'
}
if (level == 'neutral') {
return 'status-tab-active status-tab-active-neutral'
}
return 'status-tab-active'
}
function getTabTextClass(tab: string): string {
if (currentTab.value != tab) {
return ''
}
const level = getTabLevel(tab)
if (level == 'danger') {
return 'status-tab-text-active status-tab-text-active-danger'
}
if (level == 'warning') {
return 'status-tab-text-active status-tab-text-active-warning'
}
if (level == 'success') {
return 'status-tab-text-active status-tab-text-active-success'
}
if (level == 'neutral') {
return 'status-tab-text-active status-tab-text-active-neutral'
}
return 'status-tab-text-active'
}
function getTabCountClass(tab: string): string {
if (tab == 'unread' && messageStats.value.unread > 0) {
if (currentTab.value == tab) {
return 'status-tab-count-active status-tab-count-unread status-tab-count-unread-active'
}
return 'status-tab-count-unread'
}
if (currentTab.value == tab) {
return 'status-tab-count-active'
}
return ''
}
function padDatePart(value: number): string {
if (value < 10) {
return '0' + value
}
return '' + value
}
function getTodayText(): string {
const now = new Date()
const year = now.getFullYear()
const month = now.getMonth() + 1
const day = now.getDate()
return '' + year + '-' + padDatePart(month) + '-' + padDatePart(day)
}
function refreshSummary(list: Array<DeliveryMessageType>): void {
const nextStats = createEmptyMessageStats()
const todayText = getTodayText()
nextStats.all = list.length
for (let i = 0; i < list.length; i++) {
const item = list[i]
if (!item.read) {
nextStats.unread++
}
if (item.type == 'order' || item.type == 'dispatch' || item.type == 'reminder' || item.type == 'checkin') {
nextStats.dispatch++
}
if (item.type == 'exception') {
nextStats.exception++
}
if (item.type == 'result' || item.type == 'settlement') {
nextStats.result++
}
if (item.type == 'notice') {
nextStats.notice++
}
if (item.createdAt.length >= 10 && item.createdAt.substring(0, 10) == todayText) {
nextStats.today++
}
}
messageStats.value = nextStats
}
function getEmptyText(): string {
if (currentTab.value == 'unread') {
return '暂无未读消息'
}
if (currentTab.value == 'dispatch') {
return '暂无调度消息'
}
if (currentTab.value == 'exception') {
return '暂无异常消息'
}
if (currentTab.value == 'result') {
return '暂无结果消息'
}
if (currentTab.value == 'notice') {
return '暂无通知消息'
}
return '暂无消息'
}
function openMessage(orderId: string) {
if (orderId == '') {
uni.showToast({
title: '暂无关联工单',
icon: 'none'
})
return
}
uni.navigateTo({ url: '/pages/mall/delivery/orders/detail?id=' + orderId })
}
function getMessageIconText(title: string, messageType: string): string {
if (title.indexOf('异常') >= 0 || messageType == 'exception') {
return '!'
}
if (title.indexOf('新工单') >= 0 || title.indexOf('待接单') >= 0 || messageType == 'order' || messageType == 'dispatch') {
return '单'
}
if (title.indexOf('出发') >= 0 || title.indexOf('签到') >= 0 || messageType == 'reminder' || messageType == 'checkin') {
return '行'
}
if (title.indexOf('结算') >= 0 || messageType == 'settlement') {
return '结'
}
if (title.indexOf('公告') >= 0 || title.indexOf('系统') >= 0 || messageType == 'notice') {
return '告'
}
if (messageType == 'result') {
return '验'
}
return '息'
}
function getMessageLevel(title: string, messageType: string): string {
if (title.indexOf('异常') >= 0 || messageType == 'exception') {
return 'danger'
}
if (title.indexOf('新工单') >= 0 || title.indexOf('待接单') >= 0 || messageType == 'order' || messageType == 'dispatch') {
return 'warning'
}
if (title.indexOf('出发') >= 0 || title.indexOf('签到') >= 0 || messageType == 'reminder' || messageType == 'checkin') {
return 'info'
}
if (title.indexOf('结算') >= 0 || messageType == 'settlement' || messageType == 'result') {
return 'success'
}
return 'neutral'
}
function getMessageLevelClass(title: string, messageType: string): string {
const level = getMessageLevel(title, messageType)
if (level == 'danger') {
return 'message-level-danger'
}
if (level == 'warning') {
return 'message-level-warning'
}
if (level == 'info') {
return 'message-level-info'
}
if (level == 'success') {
return 'message-level-success'
}
return 'message-level-neutral'
}
function getMessageTextLevelClass(title: string, messageType: string): string {
const level = getMessageLevel(title, messageType)
if (level == 'danger') {
return 'message-text-danger'
}
if (level == 'warning') {
return 'message-text-warning'
}
if (level == 'info') {
return 'message-text-info'
}
if (level == 'success') {
return 'message-text-success'
}
return 'message-text-neutral'
}
function getMessageTitleClass(title: string, messageType: string, read: boolean): string {
const levelClass = getMessageTextLevelClass(title, messageType)
if (read) {
return 'message-title-read ' + levelClass
}
return levelClass
}
function getMessageCardClass(title: string, messageType: string, read: boolean): string {
const level = getMessageLevel(title, messageType)
let cardClass = read ? 'message-read' : 'message-unread'
if (level == 'danger') {
cardClass += ' message-card-danger'
}
return cardClass
}
function getMessageBizLine(title: string, messageType: string, orderId: string): string {
let bizText = '业务类型:服务通知'
if (messageType == 'order' || title.indexOf('新工单') >= 0 || title.indexOf('待接单') >= 0) {
bizText = '业务类型:基础照护'
} else if (messageType == 'dispatch' || title.indexOf('改派') >= 0) {
bizText = '业务类型:陪诊协助'
} else if (messageType == 'reminder' || title.indexOf('出发') >= 0) {
bizText = '业务类型:康复指导'
} else if (messageType == 'checkin' || title.indexOf('签到') >= 0) {
bizText = '业务类型:上门签到'
} else if (messageType == 'exception' || title.indexOf('异常') >= 0) {
bizText = '业务类型:异常处理'
} else if (messageType == 'result' || title.indexOf('验收') >= 0) {
bizText = '业务类型:服务验收'
} else if (messageType == 'settlement' || title.indexOf('结算') >= 0) {
bizText = '业务类型:结算通知'
} else if (messageType == 'notice' || title.indexOf('系统') >= 0 || title.indexOf('公告') >= 0) {
bizText = '业务类型:系统通知'
}
if (orderId != '') {
return bizText + ' · 关联工单可查看详情'
}
return bizText + ' · 暂无关联工单'
}
onShow(() => {
loadData()
})
</script>
<style scoped>
.delivery-messages-page {
min-height: 100%;
margin-left: -24rpx;
margin-right: -24rpx;
margin-top: -24rpx;
padding-bottom: 32rpx;
background-color: #f4f8fb;
}
.delivery-messages-hero {
padding: 72rpx 28rpx 34rpx;
border-bottom-left-radius: 36rpx;
border-bottom-right-radius: 36rpx;
background-color: #0f766e;
}
.delivery-messages-hero-top,
.delivery-messages-card-header,
.message-title-row {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.delivery-messages-hero-top {
align-items: center;
}
.delivery-messages-hero-main {
flex: 1;
padding-right: 16rpx;
}
.delivery-messages-hero-title {
font-size: 38rpx;
font-weight: 700;
color: #ffffff;
}
.delivery-messages-hero-subtitle {
margin-top: 10rpx;
font-size: 24rpx;
line-height: 34rpx;
color: rgba(255, 255, 255, 0.9);
}
.delivery-messages-hero-badge {
padding: 10rpx 18rpx;
border-radius: 999rpx;
background-color: rgba(255, 255, 255, 0.18);
}
.delivery-messages-hero-badge-text {
font-size: 24rpx;
color: #ffffff;
}
.delivery-messages-hero-tip-box {
margin-top: 28rpx;
padding: 20rpx 22rpx;
border-radius: 22rpx;
background-color: rgba(255, 255, 255, 0.16);
}
.delivery-messages-hero-tip {
font-size: 23rpx;
line-height: 34rpx;
color: rgba(255, 255, 255, 0.88);
}
.delivery-messages-card {
margin-left: 24rpx;
margin-right: 24rpx;
margin-top: 22rpx;
padding: 26rpx;
border-radius: 28rpx;
background-color: #ffffff;
box-shadow: 0 10rpx 28rpx rgba(15, 35, 55, 0.06);
}
.delivery-messages-filter-card {
margin-top: -18rpx;
position: relative;
}
.delivery-messages-card-title {
font-size: 30rpx;
font-weight: 700;
color: #16324f;
}
.delivery-messages-card-subtitle {
display: block;
margin-top: 8rpx;
font-size: 22rpx;
line-height: 34rpx;
color: #64748b;
}
.status-scroll {
margin-top: 22rpx;
width: 100%;
flex-direction: row;
}
.status-tabs-row {
flex-direction: row;
align-items: center;
padding-left: 4rpx;
padding-right: 20rpx;
padding-top: 4rpx;
padding-bottom: 4rpx;
}
.status-tab-item {
flex-shrink: 0;
height: 64rpx;
padding-left: 28rpx;
padding-right: 28rpx;
margin-right: 16rpx;
border-radius: 999rpx;
background-color: #f1f5f9;
flex-direction: row;
align-items: center;
justify-content: center;
}
.status-tab-content {
flex-direction: row;
align-items: center;
}
.status-tab-active {
background-color: #0f766e;
box-shadow: 0 8rpx 18rpx rgba(15, 118, 110, 0.18);
}
.status-tab-active-danger {
background-color: #dc2626;
box-shadow: 0 8rpx 18rpx rgba(220, 38, 38, 0.18);
}
.status-tab-active-warning {
background-color: #d97706;
box-shadow: 0 8rpx 18rpx rgba(217, 119, 6, 0.18);
}
.status-tab-active-success {
background-color: #16a34a;
box-shadow: 0 8rpx 18rpx rgba(22, 163, 74, 0.18);
}
.status-tab-active-neutral {
background-color: #475569;
box-shadow: 0 8rpx 18rpx rgba(71, 85, 105, 0.18);
}
.status-tab-text {
font-size: 26rpx;
font-weight: 500;
color: #64748b;
}
.status-tab-count {
margin-left: 10rpx;
min-width: 30rpx;
padding-left: 8rpx;
padding-right: 8rpx;
padding-top: 2rpx;
padding-bottom: 2rpx;
border-radius: 999rpx;
font-size: 20rpx;
line-height: 24rpx;
text-align: center;
color: #64748b;
background-color: rgba(148, 163, 184, 0.16);
}
.status-tab-text-active {
color: #ffffff;
font-weight: 700;
}
.status-tab-count-active {
color: #ffffff;
background-color: rgba(255, 255, 255, 0.18);
}
.status-tab-count-unread {
min-width: 34rpx;
color: #ffffff;
background-color: #ef4444;
}
.status-tab-count-unread-active {
background-color: rgba(255, 255, 255, 0.24);
}
.status-tab-text-active-danger,
.status-tab-text-active-warning,
.status-tab-text-active-success,
.status-tab-text-active-neutral {
color: #ffffff;
}
.message-summary {
flex-direction: row;
margin-top: 22rpx;
}
.summary-card {
flex: 1;
padding-top: 20rpx;
padding-bottom: 20rpx;
padding-left: 18rpx;
padding-right: 18rpx;
border-radius: 20rpx;
background-color: #f8fbfc;
margin-right: 14rpx;
}
.summary-card-accent {
background-color: #ecfdf5;
}
.summary-card-last {
margin-right: 0;
}
.summary-value {
font-size: 34rpx;
font-weight: 700;
color: #0f766e;
}
.summary-value-dark {
color: #16324f;
}
.summary-label {
margin-top: 8rpx;
font-size: 22rpx;
line-height: 30rpx;
color: #6b7a89;
}
.message-card {
margin-top: 22rpx;
padding: 24rpx;
border-radius: 24rpx;
background-color: #ffffff;
flex-direction: row;
align-items: flex-start;
box-shadow: 0 8rpx 24rpx rgba(15, 35, 55, 0.06);
border-left-width: 0rpx;
border-left-style: solid;
border-left-color: transparent;
}
.message-unread {
background-color: #f0fdfa;
}
.message-read {
background-color: #ffffff;
}
.message-icon {
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
align-items: center;
justify-content: center;
margin-right: 18rpx;
background-color: #f1f5f9;
}
.message-level-danger {
background-color: #fee2e2;
}
.message-level-warning {
background-color: #fef3c7;
}
.message-level-info {
background-color: #dbeafe;
}
.message-level-success {
background-color: #dcfce7;
}
.message-level-neutral {
background-color: #f1f5f9;
}
.message-icon-text {
font-size: 26rpx;
font-weight: 700;
color: #0f766e;
}
.message-text-danger {
color: #dc2626;
}
.message-text-warning {
color: #d97706;
}
.message-text-info {
color: #2563eb;
}
.message-text-success {
color: #16a34a;
}
.message-text-neutral {
color: #64748b;
}
.message-main {
flex: 1;
}
.message-title-row {
align-items: center;
margin-bottom: 10rpx;
}
.message-title {
font-size: 30rpx;
font-weight: 700;
color: #16324f;
}
.message-title-read {
color: #38526b;
}
.message-card-danger {
border-left-width: 8rpx;
border-left-color: #dc2626;
}
.message-badge {
font-size: 20rpx;
color: #ef4444;
background-color: #fee2e2;
border-radius: 999rpx;
padding-left: 12rpx;
padding-right: 12rpx;
padding-top: 4rpx;
padding-bottom: 4rpx;
}
.message-read-text {
font-size: 22rpx;
color: #94a3b8;
}
.message-biz-line {
display: block;
margin-top: 2rpx;
font-size: 22rpx;
line-height: 32rpx;
color: #0f766e;
}
.message-content,
.message-meta {
display: block;
margin-top: 12rpx;
font-size: 24rpx;
line-height: 36rpx;
color: #5e758c;
}
.message-meta {
font-size: 22rpx;
color: #94a3b8;
}
.empty-box {
padding: 40rpx 24rpx;
margin-top: 22rpx;
border-radius: 22rpx;
background: #f8fbfc;
align-items: center;
justify-content: center;
}
.empty-text {
font-size: 26rpx;
color: #94a3b8;
}
.delivery-messages-safe {
height: 40rpx;
}
</style>