|
@@ -40,7 +40,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 右侧:通知列表 -->
|
|
|
|
|
|
|
+ <!-- 右侧:通知列表(系统通知/订单提醒:卡片式;互动:头像+发送者+内容+日期) -->
|
|
|
<div v-if="activeTab === 'notice'" class="drawer-main-wrap">
|
|
<div v-if="activeTab === 'notice'" class="drawer-main-wrap">
|
|
|
<div class="drawer-main">
|
|
<div class="drawer-main">
|
|
|
<div v-if="currentLoading" class="empty-tip">
|
|
<div v-if="currentLoading" class="empty-tip">
|
|
@@ -50,27 +50,58 @@
|
|
|
<span style="margin-left: 8px">加载中...</span>
|
|
<span style="margin-left: 8px">加载中...</span>
|
|
|
</div>
|
|
</div>
|
|
|
<template v-else>
|
|
<template v-else>
|
|
|
- <div
|
|
|
|
|
- v-for="(item, index) in currentList"
|
|
|
|
|
- :key="item.id + '_' + index"
|
|
|
|
|
- class="notice-card"
|
|
|
|
|
- :class="{ unread: item.unread }"
|
|
|
|
|
- >
|
|
|
|
|
- <div class="card-row">
|
|
|
|
|
- <span class="card-title">
|
|
|
|
|
- {{ item.title }}
|
|
|
|
|
- <span v-if="item.unread" class="unread-dot" />
|
|
|
|
|
- </span>
|
|
|
|
|
- <span class="card-date">{{ item.date }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="card-content">
|
|
|
|
|
- {{ item.content }}
|
|
|
|
|
|
|
+ <!-- 系统通知、订单提醒:卡片式 -->
|
|
|
|
|
+ <template v-if="activeCategory !== 'related'">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(item, index) in currentList"
|
|
|
|
|
+ :key="item.id + '_' + index"
|
|
|
|
|
+ class="notice-card"
|
|
|
|
|
+ :class="{ unread: item.unread }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="card-row">
|
|
|
|
|
+ <span class="card-title">
|
|
|
|
|
+ {{ item.title }}
|
|
|
|
|
+ <span v-if="item.unread" class="unread-dot" />
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span class="card-date">{{ item.date }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-content">
|
|
|
|
|
+ {{ processContent(item) }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="card-actions">
|
|
|
|
|
+ <el-button size="small" @click="handleViewDetail(item)"> 查看详情 </el-button>
|
|
|
|
|
+ <!-- <el-button size="small" @click="handleDelete(item, index)"> 删除 </el-button> -->
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="card-actions">
|
|
|
|
|
- <el-button size="small" @click="handleViewDetail(item)"> 查看详情 </el-button>
|
|
|
|
|
- <el-button size="small" @click="handleDelete(item, index)"> 删除 </el-button>
|
|
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 互动:头像+发送者+内容+日期+删除(与商家端 inform 列表一致) -->
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(item, index) in currentList"
|
|
|
|
|
+ :key="item.id + '_' + index"
|
|
|
|
|
+ class="message-card"
|
|
|
|
|
+ :class="{ unread: item.unread }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="message-avatar">
|
|
|
|
|
+ <el-avatar :size="40" :src="item.userImage">
|
|
|
|
|
+ <el-icon><UserFilled /></el-icon>
|
|
|
|
|
+ </el-avatar>
|
|
|
|
|
+ <span v-if="item.unread" class="unread-dot message-unread-dot" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="message-body" @click="handleViewDetail(item)">
|
|
|
|
|
+ <div class="message-row">
|
|
|
|
|
+ <span class="message-sender">{{ item.userName || item.title || "未知" }}</span>
|
|
|
|
|
+ <span class="message-date">{{ item.date }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="message-content">
|
|
|
|
|
+ {{ processContent(item) }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- <div class="message-actions">
|
|
|
|
|
+ <el-button size="small" type="default" @click.stop="handleDelete(item, index)"> 删除 </el-button>
|
|
|
|
|
+ </div> -->
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
|
+ </template>
|
|
|
<div v-if="currentList.length === 0" class="empty-tip">暂无数据</div>
|
|
<div v-if="currentList.length === 0" class="empty-tip">暂无数据</div>
|
|
|
</template>
|
|
</template>
|
|
|
</div>
|
|
</div>
|
|
@@ -97,8 +128,13 @@
|
|
|
</el-icon>
|
|
</el-icon>
|
|
|
<span style="margin-left: 8px">加载中...</span>
|
|
<span style="margin-left: 8px">加载中...</span>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <!-- @click="handleMessageItemClick(item)" -->
|
|
|
<template v-else>
|
|
<template v-else>
|
|
|
- <div v-for="(item, index) in currentMessageList" :key="item.id + '_' + index" class="message-card">
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(item, index) in currentMessageList"
|
|
|
|
|
+ :key="item.id + '_' + index"
|
|
|
|
|
+ class="message-card message-card-clickable"
|
|
|
|
|
+ >
|
|
|
<div class="message-avatar">
|
|
<div class="message-avatar">
|
|
|
<el-avatar :size="40" :src="item.avatar">
|
|
<el-avatar :size="40" :src="item.avatar">
|
|
|
<el-icon><UserFilled /></el-icon>
|
|
<el-icon><UserFilled /></el-icon>
|
|
@@ -110,10 +146,7 @@
|
|
|
<span class="message-date">{{ item.date }}</span>
|
|
<span class="message-date">{{ item.date }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="message-content">
|
|
<div class="message-content">
|
|
|
- {{ item.content }}
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="message-actions">
|
|
|
|
|
- <el-button size="small" type="default" @click="handleMessageDelete(item, index)"> 删除 </el-button>
|
|
|
|
|
|
|
+ {{ processContent(item) }}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -133,7 +166,8 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref, computed, onMounted, watch } from "vue";
|
|
|
|
|
|
|
+import { ref, computed, onMounted, watch, markRaw } from "vue";
|
|
|
|
|
+import { useRouter } from "vue-router";
|
|
|
import { House, List, User, Loading, Message, UserFilled } from "@element-plus/icons-vue";
|
|
import { House, List, User, Loading, Message, UserFilled } from "@element-plus/icons-vue";
|
|
|
import { localGet } from "@/utils";
|
|
import { localGet } from "@/utils";
|
|
|
import {
|
|
import {
|
|
@@ -145,12 +179,20 @@ import {
|
|
|
} from "@/api/modules/headerNotice";
|
|
} from "@/api/modules/headerNotice";
|
|
|
import type { NoFriendMessageItem } from "@/api/modules/headerNotice";
|
|
import type { NoFriendMessageItem } from "@/api/modules/headerNotice";
|
|
|
|
|
|
|
|
|
|
+const emit = defineEmits<{ (e: "close"): void }>();
|
|
|
|
|
+const router = useRouter();
|
|
|
|
|
+
|
|
|
interface NoticeItem {
|
|
interface NoticeItem {
|
|
|
id: string;
|
|
id: string;
|
|
|
title: string;
|
|
title: string;
|
|
|
content: string;
|
|
content: string;
|
|
|
date: string;
|
|
date: string;
|
|
|
unread: boolean;
|
|
unread: boolean;
|
|
|
|
|
+ /** 互动类可能有发送者与头像 */
|
|
|
|
|
+ userName?: string;
|
|
|
|
|
+ userImage?: string;
|
|
|
|
|
+ /** 类型,用于 processContent 展示 */
|
|
|
|
|
+ type?: string;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
interface MessageItem {
|
|
interface MessageItem {
|
|
@@ -160,6 +202,14 @@ interface MessageItem {
|
|
|
date: string;
|
|
date: string;
|
|
|
unread: boolean;
|
|
unread: boolean;
|
|
|
avatar?: string;
|
|
avatar?: string;
|
|
|
|
|
+ /** 发送方 id,用于跳转聊天页 */
|
|
|
|
|
+ senderId?: string;
|
|
|
|
|
+ /** 消息类型,用于 processContent 展示 */
|
|
|
|
|
+ type?: string;
|
|
|
|
|
+ /** 语音时长等,用于 getVoiceDuration */
|
|
|
|
|
+ voiceDuration?: number;
|
|
|
|
|
+ duration?: number;
|
|
|
|
|
+ [key: string]: any;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const activeTab = ref<"notice" | "message">("notice");
|
|
const activeTab = ref<"notice" | "message">("notice");
|
|
@@ -172,16 +222,17 @@ const noticeTypeByKey: Record<string, number> = {
|
|
|
order: 2,
|
|
order: 2,
|
|
|
related: 0
|
|
related: 0
|
|
|
};
|
|
};
|
|
|
|
|
+// 与商家端一致:系统通知、订单提醒、互动(其余通知均为互动类别)。icon 用 markRaw 避免被转为响应式触发 Vue 警告
|
|
|
const noticeCategories = ref([
|
|
const noticeCategories = ref([
|
|
|
- { key: "system", title: "系统通知", icon: House, unread: 0 },
|
|
|
|
|
- { key: "order", title: "订单提醒", icon: List, unread: 0 },
|
|
|
|
|
- { key: "related", title: "与我相关", icon: User, unread: 0 }
|
|
|
|
|
|
|
+ { key: "system", title: "系统通知", icon: markRaw(House), unread: 0 },
|
|
|
|
|
+ { key: "order", title: "订单提醒", icon: markRaw(List), unread: 0 },
|
|
|
|
|
+ { key: "related", title: "互动", icon: markRaw(User), unread: 0 }
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
// 消息 Tab 分类(如图:未关注人消息、消息列表)
|
|
// 消息 Tab 分类(如图:未关注人消息、消息列表)
|
|
|
const messageCategories = ref([
|
|
const messageCategories = ref([
|
|
|
- { key: "unfollowed", title: "未关注人消息", icon: User, unread: 0 },
|
|
|
|
|
- { key: "messageList", title: "消息列表", icon: Message, unread: 0 }
|
|
|
|
|
|
|
+ { key: "unfollowed", title: "未关注人消息", icon: markRaw(User), unread: 0 },
|
|
|
|
|
+ { key: "messageList", title: "消息列表", icon: markRaw(Message), unread: 0 }
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
const listByCategory = ref<Record<string, NoticeItem[]>>({
|
|
const listByCategory = ref<Record<string, NoticeItem[]>>({
|
|
@@ -258,7 +309,11 @@ function mapNoFriendToMessageItem(item: NoFriendMessageItem): MessageItem {
|
|
|
content: item.content ?? "",
|
|
content: item.content ?? "",
|
|
|
date: dateStr,
|
|
date: dateStr,
|
|
|
unread: item.isRead === 0,
|
|
unread: item.isRead === 0,
|
|
|
- avatar: item.userImage ?? item.senderImg ?? item.storeImg ?? undefined
|
|
|
|
|
|
|
+ avatar: item.userImage ?? item.senderImg ?? item.storeImg ?? undefined,
|
|
|
|
|
+ senderId: item.senderId != null ? String(item.senderId) : undefined,
|
|
|
|
|
+ type: item.type,
|
|
|
|
|
+ voiceDuration: (item as any).voiceDuration ?? (item as any).duration,
|
|
|
|
|
+ duration: (item as any).duration
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -274,6 +329,10 @@ async function fetchNoFriendMessage() {
|
|
|
try {
|
|
try {
|
|
|
const res: any = await getNoFriendMessage({ receiverId: "store_" + receiverId });
|
|
const res: any = await getNoFriendMessage({ receiverId: "store_" + receiverId });
|
|
|
const data = res?.data ?? res;
|
|
const data = res?.data ?? res;
|
|
|
|
|
+ if (data.msg == "暂无承载数据") {
|
|
|
|
|
+ messageListByCategory.value.unfollowed = [];
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
const rawList = Array.isArray(data) ? data : data ? [data] : [];
|
|
const rawList = Array.isArray(data) ? data : data ? [data] : [];
|
|
|
const list: MessageItem[] = rawList.map((item: NoFriendMessageItem) => mapNoFriendToMessageItem(item));
|
|
const list: MessageItem[] = rawList.map((item: NoFriendMessageItem) => mapNoFriendToMessageItem(item));
|
|
|
messageListByCategory.value.unfollowed = list;
|
|
messageListByCategory.value.unfollowed = list;
|
|
@@ -328,6 +387,54 @@ function getReceiverId(): string {
|
|
|
return localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
|
|
return localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 点击消息列表项:关闭抽屉并跳转聊天页(与 storeDecoration 装修聊天一致)
|
|
|
|
|
+function handleMessageItemClick(item: MessageItem) {
|
|
|
|
|
+ console.log("item", item);
|
|
|
|
|
+ const receiverId = item.senderName;
|
|
|
|
|
+ if (!receiverId) return;
|
|
|
|
|
+ emit("close");
|
|
|
|
|
+ const params = new URLSearchParams({
|
|
|
|
|
+ receiverId: String(receiverId),
|
|
|
|
|
+ uName: encodeURIComponent(item.senderName || "用户"),
|
|
|
|
|
+ userImage: item.avatar ? encodeURIComponent(item.avatar) : ""
|
|
|
|
|
+ });
|
|
|
|
|
+ router.push(`/storeDecorationManagement/decorationChat?${params.toString()}`);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 根据类型显示内容(与商家端一致)
|
|
|
|
|
+function getVoiceDuration(item: { voiceDuration?: number; duration?: number; [key: string]: any }): string {
|
|
|
|
|
+ const sec = item?.voiceDuration ?? item?.duration ?? 0;
|
|
|
|
|
+ return sec ? `${sec}` : "0";
|
|
|
|
|
+}
|
|
|
|
|
+function processContent(item: { type?: string; content?: string; [key: string]: any }): string {
|
|
|
|
|
+ switch (String(item.type ?? "")) {
|
|
|
|
|
+ case "1":
|
|
|
|
|
+ return item.content ?? "";
|
|
|
|
|
+ case "2":
|
|
|
|
|
+ return "[图片]";
|
|
|
|
|
+ case "3":
|
|
|
|
|
+ return "[分享]";
|
|
|
|
|
+ case "4":
|
|
|
|
|
+ return "[交易请求]";
|
|
|
|
|
+ case "5":
|
|
|
|
|
+ return "[签到]";
|
|
|
|
|
+ case "6":
|
|
|
|
|
+ return "[签到确认]";
|
|
|
|
|
+ case "8":
|
|
|
|
|
+ return "[视频]";
|
|
|
|
|
+ case "9":
|
|
|
|
|
+ return "[修改交易请求]";
|
|
|
|
|
+ case "10":
|
|
|
|
|
+ return "[定位共享]";
|
|
|
|
|
+ case "11":
|
|
|
|
|
+ return "[委托]";
|
|
|
|
|
+ case "13":
|
|
|
|
|
+ return "[语音]" + getVoiceDuration(item) + '"';
|
|
|
|
|
+ default:
|
|
|
|
|
+ return item.content ?? "";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 与原有通知页面一致的 context 解析
|
|
// 与原有通知页面一致的 context 解析
|
|
|
function parseContext(context: string | undefined): string {
|
|
function parseContext(context: string | undefined): string {
|
|
|
if (!context) return "";
|
|
if (!context) return "";
|
|
@@ -339,6 +446,15 @@ function parseContext(context: string | undefined): string {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 互动类内容:商家端 context 可能为 "类型|split|内容1|split|内容2",取第一段或解析 JSON
|
|
|
|
|
+function getInteractionContent(context: string | undefined): string {
|
|
|
|
|
+ if (!context) return "";
|
|
|
|
|
+ if (typeof context === "string" && context.includes("|split|")) {
|
|
|
|
|
+ return context.split("|split|")[0]?.trim() || context;
|
|
|
|
|
+ }
|
|
|
|
|
+ return parseContext(context);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 统一请求:/alienStorePlatform/notice/getNoticeList,noticeType 系统=1、订单提醒=2、与我相关=0,分页查询
|
|
// 统一请求:/alienStorePlatform/notice/getNoticeList,noticeType 系统=1、订单提醒=2、与我相关=0,分页查询
|
|
|
async function fetchNoticeList(catKey: string) {
|
|
async function fetchNoticeList(catKey: string) {
|
|
|
const receiverId = getReceiverId();
|
|
const receiverId = getReceiverId();
|
|
@@ -360,13 +476,25 @@ async function fetchNoticeList(catKey: string) {
|
|
|
const data = res?.data ?? res;
|
|
const data = res?.data ?? res;
|
|
|
const records = data?.records ?? data?.list ?? [];
|
|
const records = data?.records ?? data?.list ?? [];
|
|
|
const total = data?.total ?? 0;
|
|
const total = data?.total ?? 0;
|
|
|
- const list: NoticeItem[] = records.map((item: any) => ({
|
|
|
|
|
- id: String(item.id),
|
|
|
|
|
- title: item.title ?? "",
|
|
|
|
|
- content: parseContext(item.context ?? item.content),
|
|
|
|
|
- date: item.createdTime ?? "",
|
|
|
|
|
- unread: !item.isRead
|
|
|
|
|
- }));
|
|
|
|
|
|
|
+ const isRelated = catKey === "related";
|
|
|
|
|
+ const list: NoticeItem[] = records.map((item: any) => {
|
|
|
|
|
+ const rawContext = item.context ?? item.content ?? "";
|
|
|
|
|
+ const content = isRelated ? getInteractionContent(rawContext) : parseContext(rawContext);
|
|
|
|
|
+ const dateRaw = item.createdTime ?? "";
|
|
|
|
|
+ const dateStr = dateRaw.includes(" ") ? dateRaw.split(" ")[0].replace(/-/g, "/") : dateRaw.replace(/-/g, "/");
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: String(item.id),
|
|
|
|
|
+ title: item.title ?? "",
|
|
|
|
|
+ content,
|
|
|
|
|
+ date: dateStr,
|
|
|
|
|
+ unread: !item.isRead,
|
|
|
|
|
+ type: item.type,
|
|
|
|
|
+ ...(isRelated && {
|
|
|
|
|
+ userName: item.userName ?? item.senderName ?? item.title ?? "",
|
|
|
|
|
+ userImage: item.userImage ?? item.storeImg ?? item.senderImg
|
|
|
|
|
+ })
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
listByCategory.value[catKey] = list;
|
|
listByCategory.value[catKey] = list;
|
|
|
paginationByCategory.value[catKey].total = total;
|
|
paginationByCategory.value[catKey].total = total;
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
@@ -448,14 +576,6 @@ function handleDelete(item: NoticeItem, index: number) {
|
|
|
const cat = noticeCategories.value.find(c => c.key === key);
|
|
const cat = noticeCategories.value.find(c => c.key === key);
|
|
|
if (cat && item.unread) cat.unread = Math.max(0, cat.unread - 1);
|
|
if (cat && item.unread) cat.unread = Math.max(0, cat.unread - 1);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-function handleMessageDelete(item: MessageItem, index: number) {
|
|
|
|
|
- const key = messageCategory.value;
|
|
|
|
|
- messageListByCategory.value[key] = currentMessageList.value.filter((_, i) => i !== index);
|
|
|
|
|
- const cat = messageCategories.value.find(c => c.key === key);
|
|
|
|
|
- if (cat && item.unread) cat.unread = Math.max(0, cat.unread - 1);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
refreshAllNoticeCategories();
|
|
refreshAllNoticeCategories();
|
|
|
});
|
|
});
|
|
@@ -576,12 +696,26 @@ watch(activeTab, val => {
|
|
|
&:last-child {
|
|
&:last-child {
|
|
|
border-bottom: none;
|
|
border-bottom: none;
|
|
|
}
|
|
}
|
|
|
|
|
+ &.message-card-clickable {
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ }
|
|
|
.message-avatar {
|
|
.message-avatar {
|
|
|
|
|
+ position: relative;
|
|
|
flex-shrink: 0;
|
|
flex-shrink: 0;
|
|
|
|
|
+ .message-unread-dot {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ width: 8px;
|
|
|
|
|
+ height: 8px;
|
|
|
|
|
+ background: var(--el-color-danger);
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
.message-body {
|
|
.message-body {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
min-width: 0;
|
|
min-width: 0;
|
|
|
|
|
+ cursor: pointer;
|
|
|
}
|
|
}
|
|
|
.message-row {
|
|
.message-row {
|
|
|
display: flex;
|
|
display: flex;
|