将组件级 i18n 对象重构为统一由全局 locales/messages.js 管理,确保中英文语言切换时正确显示对应文本。 - 新增 utils/i18n.js 国际化工具函数 - 更新 store/$t.mixin.js 翻译混入逻辑 - 迁移 pkgService、pkgBooking、pkgAccount、pkgExplore、pages 等共 58 个组件的中英文文本至全局消息表
529 lines
14 KiB
Vue
529 lines
14 KiB
Vue
<template>
|
|
<view class="message-detail-page tn-safe-area-inset-bottom">
|
|
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
|
|
<view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('消息详情') }}</view>
|
|
</tn-nav-bar>
|
|
|
|
<view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }">
|
|
<view class="hero-panel tn-margin-bottom-lg" :class="heroThemeClass">
|
|
<view class="hero-top-row">
|
|
<view class="hero-badge" :class="heroBadgeClass">{{ categoryText }}</view>
|
|
<view class="hero-icon-wrap">
|
|
<text :class="categoryIcon"></text>
|
|
</view>
|
|
</view>
|
|
<view class="hero-title tn-margin-top">{{ localizedTitle || $tr('消息通知') }}</view>
|
|
<view class="hero-meta-row tn-margin-top">
|
|
<view class="hero-meta-chip">
|
|
<text class="tn-icon-time tn-margin-right-xs"></text>
|
|
<text>{{ detail.time || '--' }}</text>
|
|
</view>
|
|
<view class="hero-meta-chip">
|
|
<text class="tn-icon-my tn-margin-right-xs"></text>
|
|
<text>{{ sourceText }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="article-header tn-margin-bottom-lg">
|
|
<view class="article-title">{{ localizedTitle || $tr('消息通知') }}</view>
|
|
<view class="article-meta tn-margin-top">
|
|
<view class="article-tag">{{ categoryText }}</view>
|
|
<view class="article-time">{{ detail.time || '--' }}</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="content-panel tn-margin-bottom-lg">
|
|
<view class="panel-title">{{ $tr('通知内容') }}</view>
|
|
<rich-text class="article-content tn-margin-top" :nodes="contentNodes"></rich-text>
|
|
</view>
|
|
|
|
<view v-if="localizedDesc && localizedDesc !== localizedContent" class="content-panel tn-margin-bottom-lg">
|
|
<view class="panel-title">{{ $tr('摘要') }}</view>
|
|
<view class="panel-desc tn-margin-top">{{ localizedDesc }}</view>
|
|
</view>
|
|
|
|
<view class="meta-panel tn-margin-bottom-lg">
|
|
<view class="meta-row">
|
|
<text class="meta-label">{{ $tr('消息来源') }}</text>
|
|
<text class="meta-value">{{ sourceText }}</text>
|
|
</view>
|
|
<view class="meta-row">
|
|
<text class="meta-label">{{ $tr('消息分类') }}</text>
|
|
<text class="meta-value">{{ categoryText }}</text>
|
|
</view>
|
|
<view v-if="detail.code" class="meta-row">
|
|
<text class="meta-label">{{ $tr('通知编码') }}</text>
|
|
<view class="meta-action-wrap">
|
|
<text class="meta-value">{{ detail.code }}</text>
|
|
<view class="copy-btn" @click="copyCode">
|
|
<text class="tn-text-xs tn-text-bold">{{ $tr('复制') }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view v-if="detail.statusHint" class="meta-row">
|
|
<text class="meta-label">{{ $tr('当前状态') }}</text>
|
|
<text class="meta-value">{{ detail.statusHint }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="action-row">
|
|
<view class="ghost-btn" @click="goBackList">
|
|
<text class="tn-text-bold">{{ $tr('返回消息页') }}</text>
|
|
</view>
|
|
<view v-if="detail.path" class="action-btn" @click="handleGoBusiness">
|
|
<text class="tn-text-bold">{{ actionButtonText }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import {
|
|
clearMessageDetailPayload,
|
|
findMessageDetailPayload,
|
|
getMessageDetailPayload
|
|
} from '../../utils/messages'
|
|
import { normalizeRichTextImages } from '../../utils/rich-text'
|
|
|
|
function escapeHtml(value = '') {
|
|
return String(value)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''')
|
|
}
|
|
|
|
function normalizeContentToRichText(value = '', emptyText = '') {
|
|
const text = String(value || '').trim()
|
|
if (!text) {
|
|
return `<p>${escapeHtml(emptyText || '')}</p>`
|
|
}
|
|
if (/<[a-z][\s\S]*>/i.test(text)) {
|
|
return normalizeRichTextImages(
|
|
text,
|
|
'display:block;width:100%;max-width:100%;height:auto;box-sizing:border-box;border-radius:16rpx;margin:16rpx 0;vertical-align:top;'
|
|
)
|
|
}
|
|
return `<div style="white-space:pre-wrap;line-height:1.9;color:#334155;font-size:30rpx;">${escapeHtml(text)}</div>`
|
|
}
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
lookupId: '',
|
|
lookupCode: '',
|
|
detail: {
|
|
id: '',
|
|
title: '',
|
|
desc: '',
|
|
content: '',
|
|
time: '',
|
|
category: 'system',
|
|
source: 'local',
|
|
code: '',
|
|
path: '',
|
|
statusHint: ''
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
language() {
|
|
return this.$getLanguage()
|
|
},
|
|
localizedTitle() {
|
|
if (this.language === 'en') {
|
|
return this.detail.titleEn || this.detail.title || this.detail.titleZh || ''
|
|
}
|
|
return this.detail.titleZh || this.detail.title || this.detail.titleEn || ''
|
|
},
|
|
localizedDesc() {
|
|
if (this.language === 'en') {
|
|
return this.detail.descEn || this.detail.desc || this.detail.descZh || ''
|
|
}
|
|
return this.detail.descZh || this.detail.desc || this.detail.descEn || ''
|
|
},
|
|
localizedContent() {
|
|
if (this.language === 'en') {
|
|
return this.detail.contentEn || this.detail.content || this.detail.contentZh || this.localizedDesc
|
|
}
|
|
return this.detail.contentZh || this.detail.content || this.detail.contentEn || this.localizedDesc
|
|
},
|
|
categoryText() {
|
|
const map = {
|
|
system: this.$tr('系统通知'),
|
|
payment: this.$tr('缴费账单'),
|
|
butler: this.$tr('管家消息')
|
|
}
|
|
return map[this.detail.category] || this.$tr('消息通知')
|
|
},
|
|
contentNodes() {
|
|
return normalizeContentToRichText(this.localizedContent || this.localizedDesc || '', this.$tr('暂无详细内容'))
|
|
},
|
|
categoryIcon() {
|
|
const map = {
|
|
system: 'tn-icon-notice-fill',
|
|
payment: 'tn-icon-coupon-fill',
|
|
butler: 'tn-icon-service-fill'
|
|
}
|
|
return map[this.detail.category] || 'tn-icon-notice-fill'
|
|
},
|
|
heroThemeClass() {
|
|
const map = {
|
|
system: 'hero-system',
|
|
payment: 'hero-payment',
|
|
butler: 'hero-butler'
|
|
}
|
|
return map[this.detail.category] || 'hero-system'
|
|
},
|
|
heroBadgeClass() {
|
|
const map = {
|
|
system: 'badge-system',
|
|
payment: 'badge-payment',
|
|
butler: 'badge-butler'
|
|
}
|
|
return map[this.detail.category] || 'badge-system'
|
|
},
|
|
actionButtonText() {
|
|
const map = {
|
|
system: this.$tr('前往相关页面'),
|
|
payment: this.$tr('去处理账单'),
|
|
butler: this.$tr('去查看服务')
|
|
}
|
|
return map[this.detail.category] || this.$tr('继续前往业务页')
|
|
},
|
|
sourceText() {
|
|
return this.detail.source === 'remote' ? this.$tr('系统通知接口') : this.$tr('本地业务消息')
|
|
}
|
|
},
|
|
onLoad(options = {}) {
|
|
this.lookupId = options.id || ''
|
|
this.lookupCode = options.code || ''
|
|
this.restoreDetail()
|
|
},
|
|
watch: {
|
|
language() {
|
|
this.restoreDetail()
|
|
}
|
|
},
|
|
onUnload() {
|
|
clearMessageDetailPayload(this.language)
|
|
},
|
|
methods: {
|
|
restoreDetail() {
|
|
const payload = getMessageDetailPayload(this.language)
|
|
const fallbackPayload = findMessageDetailPayload(this.lookupId, this.lookupCode, this.language)
|
|
const resolvedPayload = payload && Object.keys(payload).length ? payload : fallbackPayload
|
|
|
|
if (resolvedPayload && typeof resolvedPayload === 'object' && Object.keys(resolvedPayload).length) {
|
|
this.detail = {
|
|
...this.detail,
|
|
...resolvedPayload
|
|
}
|
|
return
|
|
}
|
|
|
|
this.detail = {
|
|
...this.detail,
|
|
id: this.lookupId || this.lookupCode || '',
|
|
code: this.lookupCode || '',
|
|
titleZh: this.$tr('消息通知'),
|
|
titleEn: this.$tr('消息通知'),
|
|
descZh: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
|
|
descEn: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
|
|
contentZh: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
|
|
contentEn: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
|
|
title: this.$tr('消息通知'),
|
|
desc: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
|
|
content: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
|
|
source: 'remote',
|
|
statusHint: this.$tr('消息缓存已失效')
|
|
}
|
|
},
|
|
goBackList() {
|
|
uni.navigateBack()
|
|
},
|
|
copyCode() {
|
|
if (!this.detail.code) {
|
|
return
|
|
}
|
|
uni.setClipboardData({
|
|
data: String(this.detail.code),
|
|
success: () => {
|
|
uni.showToast({
|
|
title: this.$tr('通知编号已复制'),
|
|
icon: 'success'
|
|
})
|
|
}
|
|
})
|
|
},
|
|
handleGoBusiness() {
|
|
if (!this.detail.path) {
|
|
return
|
|
}
|
|
uni.navigateTo({
|
|
url: this.detail.path
|
|
})
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.message-detail-page {
|
|
min-height: 100vh;
|
|
background: linear-gradient(145deg, #F4F7FA 0%, #EAEFF5 100%);
|
|
}
|
|
|
|
.hero-panel,
|
|
.content-panel,
|
|
.meta-panel {
|
|
background: rgba(255, 255, 255, 0.84);
|
|
border-radius: 32rpx;
|
|
padding: 32rpx;
|
|
box-shadow: 0 16rpx 48rpx rgba(15, 23, 42, 0.04), inset 0 2rpx 0 rgba(255, 255, 255, 1);
|
|
backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, 0.6);
|
|
}
|
|
|
|
.hero-panel {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.hero-panel::after {
|
|
content: '';
|
|
position: absolute;
|
|
right: -60rpx;
|
|
top: -80rpx;
|
|
width: 240rpx;
|
|
height: 240rpx;
|
|
border-radius: 50%;
|
|
opacity: 0.28;
|
|
}
|
|
|
|
.hero-system {
|
|
background: linear-gradient(135deg, rgba(37, 99, 235, 0.16) 0%, rgba(255, 255, 255, 0.88) 56%);
|
|
}
|
|
|
|
.hero-system::after {
|
|
background: radial-gradient(circle, rgba(59, 130, 246, 0.42) 0%, rgba(59, 130, 246, 0) 70%);
|
|
}
|
|
|
|
.hero-payment {
|
|
background: linear-gradient(135deg, rgba(245, 158, 11, 0.16) 0%, rgba(255, 255, 255, 0.88) 56%);
|
|
}
|
|
|
|
.hero-payment::after {
|
|
background: radial-gradient(circle, rgba(245, 158, 11, 0.34) 0%, rgba(245, 158, 11, 0) 70%);
|
|
}
|
|
|
|
.hero-butler {
|
|
background: linear-gradient(135deg, rgba(16, 185, 129, 0.16) 0%, rgba(255, 255, 255, 0.88) 56%);
|
|
}
|
|
|
|
.hero-butler::after {
|
|
background: radial-gradient(circle, rgba(16, 185, 129, 0.34) 0%, rgba(16, 185, 129, 0) 70%);
|
|
}
|
|
|
|
.hero-top-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.hero-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 10rpx 20rpx;
|
|
border-radius: 999rpx;
|
|
font-size: 20rpx;
|
|
letter-spacing: 4rpx;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.badge-system {
|
|
color: #1d4ed8;
|
|
background: rgba(29, 78, 216, 0.12);
|
|
}
|
|
|
|
.badge-payment {
|
|
color: #b45309;
|
|
background: rgba(245, 158, 11, 0.14);
|
|
}
|
|
|
|
.badge-butler {
|
|
color: #047857;
|
|
background: rgba(16, 185, 129, 0.14);
|
|
}
|
|
|
|
.hero-icon-wrap {
|
|
width: 88rpx;
|
|
height: 88rpx;
|
|
border-radius: 24rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 40rpx;
|
|
color: #ffffff;
|
|
background: linear-gradient(135deg, #0f172a 0%, #475569 100%);
|
|
box-shadow: 0 16rpx 32rpx rgba(15, 23, 42, 0.14);
|
|
}
|
|
|
|
.hero-title {
|
|
font-size: 44rpx;
|
|
font-weight: 700;
|
|
color: #0f172a;
|
|
line-height: 1.4;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.hero-meta,
|
|
.panel-desc,
|
|
.meta-label,
|
|
.meta-value {
|
|
font-size: 24rpx;
|
|
line-height: 1.8;
|
|
color: #64748b;
|
|
}
|
|
|
|
.hero-meta-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 16rpx;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.hero-meta-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 10rpx 18rpx;
|
|
border-radius: 999rpx;
|
|
font-size: 22rpx;
|
|
color: #475569;
|
|
background: rgba(148, 163, 184, 0.1);
|
|
}
|
|
|
|
.article-header {
|
|
padding: 40rpx 32rpx;
|
|
background: #ffffff;
|
|
border-radius: 32rpx;
|
|
box-shadow: 0 16rpx 48rpx rgba(15, 23, 42, 0.04);
|
|
}
|
|
|
|
.article-title {
|
|
font-size: 40rpx;
|
|
line-height: 1.5;
|
|
font-weight: 900;
|
|
color: #0f172a;
|
|
}
|
|
|
|
.article-meta {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.article-tag {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 8rpx 18rpx;
|
|
border-radius: 10rpx;
|
|
color: #1d4ed8;
|
|
font-size: 22rpx;
|
|
font-weight: 700;
|
|
background: rgba(29, 78, 216, 0.1);
|
|
}
|
|
|
|
.article-time {
|
|
font-size: 24rpx;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.panel-title {
|
|
font-size: 30rpx;
|
|
font-weight: 700;
|
|
color: #0f172a;
|
|
}
|
|
|
|
.meta-row + .meta-row {
|
|
margin-top: 18rpx;
|
|
}
|
|
|
|
.meta-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
gap: 24rpx;
|
|
}
|
|
|
|
.meta-action-wrap {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14rpx;
|
|
}
|
|
|
|
.meta-value {
|
|
color: #111827;
|
|
text-align: right;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.copy-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 72rpx;
|
|
height: 44rpx;
|
|
padding: 0 14rpx;
|
|
border-radius: 999rpx;
|
|
color: #1d4ed8;
|
|
background: rgba(29, 78, 216, 0.1);
|
|
}
|
|
|
|
.article-content {
|
|
font-size: 30rpx;
|
|
color: #334155;
|
|
line-height: 1.9;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.action-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 92rpx;
|
|
border-radius: 999rpx;
|
|
color: #0f172a;
|
|
background: linear-gradient(135deg, $brand-primary 0%, #c7eb65 100%);
|
|
box-shadow: 0 16rpx 32rpx rgba($brand-primary, 0.25);
|
|
}
|
|
|
|
.action-row {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.action-row > view {
|
|
flex: 1;
|
|
}
|
|
|
|
.ghost-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 92rpx;
|
|
border-radius: 999rpx;
|
|
color: #0f172a;
|
|
background: rgba(255, 255, 255, 0.84);
|
|
border: 1px solid rgba(148, 163, 184, 0.2);
|
|
box-shadow: 0 12rpx 24rpx rgba(15, 23, 42, 0.04);
|
|
}
|
|
</style>
|