feat:0820需求
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
<template>
|
||||
<view class="message-page">
|
||||
<TopSafe></TopSafe>
|
||||
<Header title="消息" :isReturn="true" @return="onReturnClick"></Header>
|
||||
|
||||
<view class="message-container">
|
||||
<view class="message-item" v-for="item in messageList" :key="item.id" @click="onItemClick(item)">
|
||||
<view class="item-icon-wrap" :class="item.iconType">
|
||||
<image class="item-icon" :src="item.icon" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<view class="item-content">
|
||||
<view class="title-row">
|
||||
<text class="item-title">{{ item.title }}</text>
|
||||
<text class="item-badge" v-if="item.badge">{{ item.badge }}</text>
|
||||
</view>
|
||||
<text class="item-desc">{{ item.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<qiaobao-assistant page-key="packages/message/message" title="消息" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Header from "@/components/common/header.vue";
|
||||
import TopSafe from "@/components/common/top-safe.nvue";
|
||||
import { getNewsList } from "@/api/news.js";
|
||||
import {
|
||||
CONTENT_TYPE,
|
||||
getCurrentMember,
|
||||
getMessages,
|
||||
getUnreadCount,
|
||||
} from "@/uni_modules/hashmall-customer-service/js_sdk/api.js";
|
||||
import { getStorageFun, TOKEN_NAME, USER_DATA } from "@/utils/auth.js";
|
||||
import {
|
||||
KEFU_REALTIME_EVENT,
|
||||
startKefuRealtime,
|
||||
} from "@/uni_modules/hashmall-customer-service/js_sdk/kefu-realtime.js";
|
||||
|
||||
const KEFU_DEFAULT_DESC = "点击联系平台客服";
|
||||
const NOTICE_DEFAULT_DESC = "暂无系统公告";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Header,
|
||||
TopSafe,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
refreshing: false,
|
||||
realtimeRefreshTimer: null,
|
||||
messageList: [
|
||||
{
|
||||
id: "kefu",
|
||||
title: "平台客服",
|
||||
badge: "",
|
||||
desc: KEFU_DEFAULT_DESC,
|
||||
iconType: "kefu",
|
||||
icon: "https://static.tbmall.xin/static/plat-customer-service.png",
|
||||
path: "/pages/rwa_package/kefu/kefu",
|
||||
},
|
||||
{
|
||||
id: "system",
|
||||
title: "系统公告",
|
||||
badge: "",
|
||||
desc: NOTICE_DEFAULT_DESC,
|
||||
iconType: "system",
|
||||
icon: "https://static.tbmall.xin/static/sys-customer-service.png",
|
||||
path: "/pages/login_package/announcement/announcement",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
uni.$on(KEFU_REALTIME_EVENT, this.handleKefuRealtimeEvent);
|
||||
},
|
||||
onShow() {
|
||||
this.refreshMessageCenter();
|
||||
startKefuRealtime();
|
||||
},
|
||||
onUnload() {
|
||||
uni.$off(KEFU_REALTIME_EVENT, this.handleKefuRealtimeEvent);
|
||||
if (this.realtimeRefreshTimer) clearTimeout(this.realtimeRefreshTimer);
|
||||
this.realtimeRefreshTimer = null;
|
||||
},
|
||||
methods: {
|
||||
handleKefuRealtimeEvent(event) {
|
||||
if (!event || !String(event.type || "").startsWith("kefu_")) return;
|
||||
// A single business operation may emit a message and a conversation event.
|
||||
// Coalesce them so the summary endpoint is read only once.
|
||||
if (this.realtimeRefreshTimer) clearTimeout(this.realtimeRefreshTimer);
|
||||
this.realtimeRefreshTimer = setTimeout(() => {
|
||||
this.realtimeRefreshTimer = null;
|
||||
this.loadKefuSummary();
|
||||
}, 120);
|
||||
},
|
||||
getMessageItem(id) {
|
||||
return this.messageList.find((item) => item.id === id);
|
||||
},
|
||||
formatBadge(value) {
|
||||
const count = Math.max(0, Number(value) || 0);
|
||||
if (!count) return "";
|
||||
return count > 99 ? "99+" : String(count);
|
||||
},
|
||||
parseStoredUser() {
|
||||
const value = getStorageFun(USER_DATA);
|
||||
if (!value) return null;
|
||||
if (typeof value === "object") {
|
||||
return value.id ? value : { ...value, id: value.userId };
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return parsed.id ? parsed : { ...parsed, id: parsed.userId };
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
withTimeout(promise, timeout = 5000) {
|
||||
return Promise.race([
|
||||
promise,
|
||||
new Promise((resolve) => setTimeout(() => resolve(null), timeout)),
|
||||
]);
|
||||
},
|
||||
normalizeKefuDesc(content, contentType) {
|
||||
const type = Number(contentType || 0);
|
||||
if (type === CONTENT_TYPE.IMAGE) return "[图片]";
|
||||
if (type === CONTENT_TYPE.FILE) return "[文件]";
|
||||
if (type === CONTENT_TYPE.AUDIO) return "[语音]";
|
||||
if (type === CONTENT_TYPE.PRODUCT) return "[商品]";
|
||||
if (type === CONTENT_TYPE.ORDER) return "[订单]";
|
||||
if (type === CONTENT_TYPE.RECALLED) return "消息已撤回";
|
||||
|
||||
const text = String(content || "").trim();
|
||||
if (!text) return KEFU_DEFAULT_DESC;
|
||||
if (type === CONTENT_TYPE.QUOTE) {
|
||||
try {
|
||||
const quote = JSON.parse(text);
|
||||
return String(quote.text || quote.content || "[引用消息]");
|
||||
} catch (error) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
},
|
||||
async loadKefuSummary() {
|
||||
const item = this.getMessageItem("kefu");
|
||||
if (!item) return;
|
||||
|
||||
if (!getStorageFun(TOKEN_NAME)) {
|
||||
item.badge = "";
|
||||
item.desc = KEFU_DEFAULT_DESC;
|
||||
return;
|
||||
}
|
||||
|
||||
let member = this.parseStoredUser();
|
||||
if (!member || !member.id) {
|
||||
try {
|
||||
member = await this.withTimeout(getCurrentMember());
|
||||
} catch (error) {
|
||||
member = null;
|
||||
}
|
||||
}
|
||||
if (!member || !member.id) return;
|
||||
|
||||
const results = await Promise.allSettled([
|
||||
getUnreadCount(member.id),
|
||||
getMessages({ userId: member.id, page: 1, pageSize: 20 }),
|
||||
]);
|
||||
|
||||
if (results[0].status === "fulfilled") {
|
||||
item.badge = this.formatBadge(results[0].value);
|
||||
}
|
||||
if (results[1].status === "fulfilled") {
|
||||
const result = results[1].value || {};
|
||||
const conversation = result.conversation || {};
|
||||
const rows = (result.messages && result.messages.entitys) || [];
|
||||
const latest = rows.length ? rows[rows.length - 1] : {};
|
||||
item.desc = this.normalizeKefuDesc(
|
||||
conversation.lastMessageContent || latest.content,
|
||||
conversation.lastMessageContentType || latest.contentType,
|
||||
);
|
||||
}
|
||||
},
|
||||
async loadNoticeSummary() {
|
||||
const item = this.getMessageItem("system");
|
||||
if (!item) return;
|
||||
try {
|
||||
const result = await getNewsList({
|
||||
category: 2,
|
||||
type: 0,
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
});
|
||||
const rows = (result && result.data && result.data.entitys) || [];
|
||||
const latest = rows[0];
|
||||
if (!latest) {
|
||||
item.badge = "";
|
||||
item.desc = NOTICE_DEFAULT_DESC;
|
||||
return;
|
||||
}
|
||||
item.desc = latest.subTitle || latest.title || NOTICE_DEFAULT_DESC;
|
||||
const readId = uni.getStorageSync("isReadNews");
|
||||
item.badge = String(readId || "") === String(latest.id || "") ? "" : "1";
|
||||
} catch (error) {
|
||||
item.badge = "";
|
||||
item.desc = NOTICE_DEFAULT_DESC;
|
||||
}
|
||||
},
|
||||
async refreshMessageCenter() {
|
||||
if (this.refreshing) return;
|
||||
this.refreshing = true;
|
||||
try {
|
||||
await Promise.allSettled([
|
||||
this.loadKefuSummary(),
|
||||
this.loadNoticeSummary(),
|
||||
]);
|
||||
} finally {
|
||||
this.refreshing = false;
|
||||
}
|
||||
},
|
||||
onReturnClick() {
|
||||
const pages = getCurrentPages();
|
||||
if (pages && pages.length > 1) {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail: () => {
|
||||
uni.switchTab({ url: "/pages/home/home" });
|
||||
},
|
||||
});
|
||||
} else {
|
||||
uni.switchTab({ url: "/pages/home/home" });
|
||||
}
|
||||
},
|
||||
onItemClick(item) {
|
||||
if (item.path) {
|
||||
uni.navigateTo({
|
||||
url: item.path,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.message-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f7f8fa;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
background-color: #ffffff;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 30rpx 32rpx;
|
||||
position: relative;
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:not(:last-child)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 152rpx;
|
||||
right: 0rpx;
|
||||
bottom: 0;
|
||||
height: 1rpx;
|
||||
background-color: #f2f3f5;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
.item-icon-wrap {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.item-icon {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-left: 24rpx;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
line-height: 44rpx;
|
||||
}
|
||||
|
||||
.item-badge {
|
||||
background-color: #f53f3f;
|
||||
color: #ffffff;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
min-width: 32rpx;
|
||||
height: 32rpx;
|
||||
line-height: 32rpx;
|
||||
border-radius: 16rpx;
|
||||
text-align: center;
|
||||
padding: 0 8rpx;
|
||||
margin-left: 12rpx;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
font-size: 26rpx;
|
||||
color: #86909c;
|
||||
line-height: 36rpx;
|
||||
margin-top: 8rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user