feat(MessageRecord): 添加小程序消息组件并优化消息处理逻辑
将小程序消息处理逻辑抽离为独立组件SmallProgramMessage,优化代码结构 新增对多种小程序类型的样式支持,包括横向布局和垂直图片布局 增强文件消息处理,支持更多文件类型和图标显示
This commit is contained in:
26
Cunkebao/dist/.vite/manifest.json
vendored
26
Cunkebao/dist/.vite/manifest.json
vendored
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"_charts-CtV6DO5_.js": {
|
||||
"file": "assets/charts-CtV6DO5_.js",
|
||||
"_charts-ghR_XExL.js": {
|
||||
"file": "assets/charts-ghR_XExL.js",
|
||||
"name": "charts",
|
||||
"imports": [
|
||||
"_ui-BRTknrR5.js",
|
||||
"_ui-J9wtlgqT.js",
|
||||
"_vendor-BPPoWDlG.js"
|
||||
]
|
||||
},
|
||||
"_ui-BRTknrR5.js": {
|
||||
"file": "assets/ui-BRTknrR5.js",
|
||||
"_ui-D0C0OGrH.css": {
|
||||
"file": "assets/ui-D0C0OGrH.css",
|
||||
"src": "_ui-D0C0OGrH.css"
|
||||
},
|
||||
"_ui-J9wtlgqT.js": {
|
||||
"file": "assets/ui-J9wtlgqT.js",
|
||||
"name": "ui",
|
||||
"imports": [
|
||||
"_vendor-BPPoWDlG.js"
|
||||
@@ -17,10 +21,6 @@
|
||||
"assets/ui-D0C0OGrH.css"
|
||||
]
|
||||
},
|
||||
"_ui-D0C0OGrH.css": {
|
||||
"file": "assets/ui-D0C0OGrH.css",
|
||||
"src": "_ui-D0C0OGrH.css"
|
||||
},
|
||||
"_utils-DiZV3oaL.js": {
|
||||
"file": "assets/utils-DiZV3oaL.js",
|
||||
"name": "utils",
|
||||
@@ -33,18 +33,18 @@
|
||||
"name": "vendor"
|
||||
},
|
||||
"index.html": {
|
||||
"file": "assets/index-DGdErvda.js",
|
||||
"file": "assets/index-CTEriEiT.js",
|
||||
"name": "index",
|
||||
"src": "index.html",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_vendor-BPPoWDlG.js",
|
||||
"_utils-DiZV3oaL.js",
|
||||
"_ui-BRTknrR5.js",
|
||||
"_charts-CtV6DO5_.js"
|
||||
"_ui-J9wtlgqT.js",
|
||||
"_charts-ghR_XExL.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/index-DoT8YtM8.css"
|
||||
"assets/index-ZHlr-6NP.css"
|
||||
]
|
||||
}
|
||||
}
|
||||
8
Cunkebao/dist/index.html
vendored
8
Cunkebao/dist/index.html
vendored
@@ -11,13 +11,13 @@
|
||||
</style>
|
||||
<!-- 引入 uni-app web-view SDK(必须) -->
|
||||
<script type="text/javascript" src="/websdk.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DGdErvda.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CTEriEiT.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-BPPoWDlG.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/utils-DiZV3oaL.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/ui-BRTknrR5.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/charts-CtV6DO5_.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/ui-J9wtlgqT.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/charts-ghR_XExL.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/ui-D0C0OGrH.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DoT8YtM8.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-ZHlr-6NP.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
// 通用消息文本样式
|
||||
.messageText {
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
// 小程序消息基础样式
|
||||
.miniProgramMessage {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
|
||||
// 通用小程序卡片基础样式
|
||||
.miniProgramCard {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #e1e8ed;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
width: 280px;
|
||||
min-height: 64px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 通用小程序元素样式
|
||||
.miniProgramThumb {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
object-fit: cover;
|
||||
background: linear-gradient(135deg, #f0f2f5 0%, #e6f7ff 100%);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.2s ease;
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.miniProgramInfo {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.miniProgramTitle {
|
||||
padding-left: 16px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 4px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.miniProgramApp {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
line-height: 1.2;
|
||||
font-weight: 500;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// 类型1小程序样式(默认横向布局)
|
||||
.miniProgramType1 {
|
||||
// 继承基础样式,无需额外定义
|
||||
}
|
||||
|
||||
// 类型2小程序样式(垂直图片布局)
|
||||
.miniProgramType2 {
|
||||
.miniProgramCardType2 {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
min-height: 220px;
|
||||
max-width: 280px;
|
||||
|
||||
.miniProgramAppTop {
|
||||
padding: 12px 16px 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
background: #f8f9fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: "📱";
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.miniProgramImageArea {
|
||||
width: calc(100% - 32px);
|
||||
height: 0;
|
||||
padding-bottom: 75%; // 4:3 宽高比
|
||||
margin: 0px 16px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #f8f9fa;
|
||||
|
||||
.miniProgramImage {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
border: 0.5px solid #e1e8ed;
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.miniProgramContent {
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.miniProgramTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #212529;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.miniProgramIdentifier {
|
||||
font-size: 11px;
|
||||
color: #6c757d;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
gap: 3px;
|
||||
|
||||
&::before {
|
||||
content: "🏷️";
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 文章类型消息样式
|
||||
.articleMessage {
|
||||
.articleCard {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 16px;
|
||||
min-height: auto;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.articleTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 12px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.articleContent {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.articleTextArea {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.articleDescription {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.articleImageArea {
|
||||
flex-shrink: 0;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.articleImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
// 文件消息样式
|
||||
.fileMessage {
|
||||
.fileCard {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
max-width: 250px;
|
||||
|
||||
&:hover {
|
||||
background: #f0f0f0;
|
||||
border-color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.fileIcon {
|
||||
font-size: 24px;
|
||||
color: #1890ff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fileInfo {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fileName {
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.fileAction {
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
// 小程序消息移动端适配
|
||||
.miniProgramMessage {
|
||||
.miniProgramCard {
|
||||
max-width: 260px;
|
||||
padding: 10px 14px;
|
||||
min-height: 56px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.miniProgramThumb {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.miniProgramTitle {
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.miniProgramApp {
|
||||
font-size: 11px;
|
||||
padding: 1px 4px;
|
||||
|
||||
&::before {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
import React from "react";
|
||||
import { parseWeappMsgStr } from "@/utils/common";
|
||||
import styles from "./SmallProgramMessage.module.scss";
|
||||
|
||||
interface SmallProgramMessageProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const SmallProgramMessage: React.FC<SmallProgramMessageProps> = ({
|
||||
content,
|
||||
}) => {
|
||||
// 统一的错误消息渲染函数
|
||||
const renderErrorMessage = (fallbackText: string) => (
|
||||
<div className={styles.messageText}>{fallbackText}</div>
|
||||
);
|
||||
|
||||
if (typeof content !== "string" || !content.trim()) {
|
||||
return renderErrorMessage("[小程序/文章/文件消息 - 无效内容]");
|
||||
}
|
||||
|
||||
try {
|
||||
const trimmedContent = content.trim();
|
||||
|
||||
// 尝试解析JSON格式的消息
|
||||
if (trimmedContent.startsWith("{") && trimmedContent.endsWith("}")) {
|
||||
const messageData = JSON.parse(trimmedContent);
|
||||
|
||||
// 处理文章类型消息
|
||||
if (messageData.type === "link" && messageData.title && messageData.url) {
|
||||
const { title, desc, thumbPath, url } = messageData;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.miniProgramMessage} ${styles.articleMessage}`}
|
||||
>
|
||||
<div
|
||||
className={`${styles.miniProgramCard} ${styles.articleCard}`}
|
||||
onClick={() => window.open(url, "_blank")}
|
||||
>
|
||||
{/* 标题在第一行 */}
|
||||
<div className={styles.articleTitle}>{title}</div>
|
||||
|
||||
{/* 下方:文字在左,图片在右 */}
|
||||
<div className={styles.articleContent}>
|
||||
<div className={styles.articleTextArea}>
|
||||
{desc && (
|
||||
<div className={styles.articleDescription}>{desc}</div>
|
||||
)}
|
||||
</div>
|
||||
{thumbPath && (
|
||||
<div className={styles.articleImageArea}>
|
||||
<img
|
||||
src={thumbPath}
|
||||
alt="文章缩略图"
|
||||
className={styles.articleImage}
|
||||
onError={e => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.miniProgramApp}>文章</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 处理小程序消息 - 统一使用parseWeappMsgStr解析
|
||||
if (messageData.type === "miniprogram" && messageData.contentXml) {
|
||||
try {
|
||||
const parsedData = parseWeappMsgStr(trimmedContent);
|
||||
|
||||
if (parsedData.appmsg) {
|
||||
const { appmsg } = parsedData;
|
||||
const title = appmsg.title || "小程序消息";
|
||||
const appName =
|
||||
appmsg.sourcedisplayname || appmsg.appname || "小程序";
|
||||
|
||||
// 获取小程序类型
|
||||
const miniProgramType =
|
||||
appmsg.weappinfo && appmsg.weappinfo.type
|
||||
? parseInt(appmsg.weappinfo.type)
|
||||
: 1;
|
||||
|
||||
// 根据type类型渲染不同布局
|
||||
if (miniProgramType === 2) {
|
||||
// 类型2:图片区域布局
|
||||
return (
|
||||
<div
|
||||
className={`${styles.miniProgramMessage} ${styles.miniProgramType2}`}
|
||||
>
|
||||
<div
|
||||
className={`${styles.miniProgramCard} ${styles.miniProgramCardType2}`}
|
||||
>
|
||||
<div className={styles.miniProgramAppTop}>{appName}</div>
|
||||
<div className={styles.miniProgramTitle}>{title}</div>
|
||||
<div className={styles.miniProgramImageArea}>
|
||||
<img
|
||||
src={parsedData.previewImage}
|
||||
alt="小程序图片"
|
||||
className={styles.miniProgramImage}
|
||||
onError={e => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.miniProgramContent}>
|
||||
<div className={styles.miniProgramIdentifier}>小程序</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// 默认类型:横向布局
|
||||
return (
|
||||
<div
|
||||
className={`${styles.miniProgramMessage} ${styles.miniProgramType1}`}
|
||||
>
|
||||
<div className={styles.miniProgramCard}>
|
||||
<img
|
||||
src={parsedData.previewImage}
|
||||
alt="小程序缩略图"
|
||||
className={styles.miniProgramThumb}
|
||||
onError={e => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
<div className={styles.miniProgramInfo}>
|
||||
<div className={styles.miniProgramTitle}>{title}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.miniProgramApp}>{appName}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error("parseWeappMsgStr解析失败:", parseError);
|
||||
return renderErrorMessage("[小程序消息 - 解析失败]");
|
||||
}
|
||||
}
|
||||
|
||||
// 验证传统JSON格式的小程序数据结构
|
||||
if (
|
||||
messageData &&
|
||||
typeof messageData === "object" &&
|
||||
(messageData.title || messageData.appName)
|
||||
) {
|
||||
return (
|
||||
<div className={styles.miniProgramMessage}>
|
||||
<div className={styles.miniProgramCard}>
|
||||
{messageData.thumb && (
|
||||
<img
|
||||
src={messageData.thumb}
|
||||
alt="小程序缩略图"
|
||||
className={styles.miniProgramThumb}
|
||||
onError={e => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.miniProgramInfo}>
|
||||
<div className={styles.miniProgramTitle}>
|
||||
{messageData.title || "小程序消息"}
|
||||
</div>
|
||||
{messageData.appName && (
|
||||
<div className={styles.miniProgramApp}>
|
||||
{messageData.appName}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 增强的文件消息处理
|
||||
const isFileUrl =
|
||||
content.startsWith("http") ||
|
||||
content.startsWith("https") ||
|
||||
content.startsWith("file://") ||
|
||||
/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|zip|rar|7z)$/i.test(content);
|
||||
|
||||
if (isFileUrl) {
|
||||
// 尝试从URL中提取文件名
|
||||
const fileName = content.split("/").pop()?.split("?")[0] || "文件";
|
||||
const fileExtension = fileName.split(".").pop()?.toLowerCase();
|
||||
|
||||
// 根据文件类型选择图标
|
||||
let fileIcon = "📄";
|
||||
if (fileExtension) {
|
||||
const iconMap: { [key: string]: string } = {
|
||||
pdf: "📕",
|
||||
doc: "📘",
|
||||
docx: "📘",
|
||||
xls: "📗",
|
||||
xlsx: "📗",
|
||||
ppt: "📙",
|
||||
pptx: "📙",
|
||||
txt: "📝",
|
||||
zip: "🗜️",
|
||||
rar: "🗜️",
|
||||
"7z": "🗜️",
|
||||
jpg: "🖼️",
|
||||
jpeg: "🖼️",
|
||||
png: "🖼️",
|
||||
gif: "🖼️",
|
||||
mp4: "🎬",
|
||||
avi: "🎬",
|
||||
mov: "🎬",
|
||||
mp3: "🎵",
|
||||
wav: "🎵",
|
||||
flac: "🎵",
|
||||
};
|
||||
fileIcon = iconMap[fileExtension] || "📄";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.fileMessage}>
|
||||
<div className={styles.fileCard}>
|
||||
<div className={styles.fileIcon}>{fileIcon}</div>
|
||||
<div className={styles.fileInfo}>
|
||||
<div className={styles.fileName}>
|
||||
{fileName.length > 20
|
||||
? fileName.substring(0, 20) + "..."
|
||||
: fileName}
|
||||
</div>
|
||||
<div
|
||||
className={styles.fileAction}
|
||||
onClick={() => {
|
||||
try {
|
||||
window.open(content, "_blank");
|
||||
} catch (e) {
|
||||
console.error("文件打开失败:", e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
点击查看
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return renderErrorMessage("[小程序/文件消息]");
|
||||
} catch (e) {
|
||||
console.warn("小程序/文件消息解析失败:", e);
|
||||
return renderErrorMessage("[小程序/文件消息 - 解析失败]");
|
||||
}
|
||||
};
|
||||
|
||||
export default SmallProgramMessage;
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
PlayCircleFilled,
|
||||
} from "@ant-design/icons";
|
||||
import AudioMessage from "./components/AudioMessage/AudioMessage";
|
||||
import SmallProgramMessage from "./components/SmallProgramMessage";
|
||||
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
||||
import { formatWechatTime, parseWeappMsgStr } from "@/utils/common";
|
||||
import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji";
|
||||
@@ -394,258 +395,7 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
|
||||
return <AudioMessage audioUrl={content} msgId={String(msg.id)} />;
|
||||
|
||||
case 49: // 小程序/文章/其他:图文、文件
|
||||
if (typeof content !== "string" || !content.trim()) {
|
||||
return renderErrorMessage("[小程序/文章/文件消息 - 无效内容]");
|
||||
}
|
||||
|
||||
try {
|
||||
const trimmedContent = content.trim();
|
||||
|
||||
// 尝试解析JSON格式的消息
|
||||
if (trimmedContent.startsWith("{") && trimmedContent.endsWith("}")) {
|
||||
const messageData = JSON.parse(trimmedContent);
|
||||
|
||||
// 处理文章类型消息
|
||||
if (
|
||||
messageData.type === "link" &&
|
||||
messageData.title &&
|
||||
messageData.url
|
||||
) {
|
||||
const { title, desc, thumbPath, url } = messageData;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.miniProgramMessage} ${styles.articleMessage}`}
|
||||
>
|
||||
<div
|
||||
className={`${styles.miniProgramCard} ${styles.articleCard}`}
|
||||
onClick={() => window.open(url, "_blank")}
|
||||
>
|
||||
{/* 标题在第一行 */}
|
||||
<div className={styles.articleTitle}>{title}</div>
|
||||
|
||||
{/* 下方:文字在左,图片在右 */}
|
||||
<div className={styles.articleContent}>
|
||||
<div className={styles.articleTextArea}>
|
||||
{desc && (
|
||||
<div className={styles.articleDescription}>
|
||||
{desc}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{thumbPath && (
|
||||
<div className={styles.articleImageArea}>
|
||||
<img
|
||||
src={thumbPath}
|
||||
alt="文章缩略图"
|
||||
className={styles.articleImage}
|
||||
onError={e => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.miniProgramApp}>文章</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 处理小程序消息 - 统一使用parseWeappMsgStr解析
|
||||
if (messageData.type === "miniprogram" && messageData.contentXml) {
|
||||
try {
|
||||
const parsedData = parseWeappMsgStr(trimmedContent);
|
||||
|
||||
if (parsedData.appmsg) {
|
||||
const { appmsg } = parsedData;
|
||||
const title = appmsg.title || "小程序消息";
|
||||
const appName =
|
||||
appmsg.sourcedisplayname || appmsg.appname || "小程序";
|
||||
|
||||
// 获取小程序类型
|
||||
const miniProgramType =
|
||||
appmsg.weappinfo && appmsg.weappinfo.type
|
||||
? parseInt(appmsg.weappinfo.type)
|
||||
: 1;
|
||||
|
||||
// 根据type类型渲染不同布局
|
||||
if (miniProgramType === 2) {
|
||||
// 类型2:图片区域布局
|
||||
return (
|
||||
<div
|
||||
className={`${styles.miniProgramMessage} ${styles.miniProgramType2}`}
|
||||
>
|
||||
<div
|
||||
className={`${styles.miniProgramCard} ${styles.miniProgramCardType2}`}
|
||||
>
|
||||
<div className={styles.miniProgramAppTop}>
|
||||
{appName}
|
||||
</div>
|
||||
<div className={styles.miniProgramTitle}>{title}</div>
|
||||
<div className={styles.miniProgramImageArea}>
|
||||
<img
|
||||
src={parsedData.previewImage}
|
||||
alt="小程序图片"
|
||||
className={styles.miniProgramImage}
|
||||
onError={e => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.miniProgramContent}>
|
||||
<div className={styles.miniProgramIdentifier}>
|
||||
小程序
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// 默认类型:横向布局
|
||||
return (
|
||||
<div
|
||||
className={`${styles.miniProgramMessage} ${styles.miniProgramType1}`}
|
||||
>
|
||||
<div className={styles.miniProgramCard}>
|
||||
<img
|
||||
src={parsedData.previewImage}
|
||||
alt="小程序缩略图"
|
||||
className={styles.miniProgramThumb}
|
||||
onError={e => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
<div className={styles.miniProgramInfo}>
|
||||
<div className={styles.miniProgramTitle}>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.miniProgramApp}>{appName}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error("parseWeappMsgStr解析失败:", parseError);
|
||||
return renderErrorMessage("[小程序消息 - 解析失败]");
|
||||
}
|
||||
}
|
||||
|
||||
// 验证传统JSON格式的小程序数据结构
|
||||
if (
|
||||
messageData &&
|
||||
typeof messageData === "object" &&
|
||||
(messageData.title || messageData.appName)
|
||||
) {
|
||||
return (
|
||||
<div className={styles.miniProgramMessage}>
|
||||
<div className={styles.miniProgramCard}>
|
||||
{messageData.thumb && (
|
||||
<img
|
||||
src={messageData.thumb}
|
||||
alt="小程序缩略图"
|
||||
className={styles.miniProgramThumb}
|
||||
onError={e => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.miniProgramInfo}>
|
||||
<div className={styles.miniProgramTitle}>
|
||||
{messageData.title || "小程序消息"}
|
||||
</div>
|
||||
{messageData.appName && (
|
||||
<div className={styles.miniProgramApp}>
|
||||
{messageData.appName}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 增强的文件消息处理
|
||||
const isFileUrl =
|
||||
content.startsWith("http") ||
|
||||
content.startsWith("https") ||
|
||||
content.startsWith("file://") ||
|
||||
/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|zip|rar|7z)$/i.test(content);
|
||||
|
||||
if (isFileUrl) {
|
||||
// 尝试从URL中提取文件名
|
||||
const fileName = content.split("/").pop()?.split("?")[0] || "文件";
|
||||
const fileExtension = fileName.split(".").pop()?.toLowerCase();
|
||||
|
||||
// 根据文件类型选择图标
|
||||
let fileIcon = "📄";
|
||||
if (fileExtension) {
|
||||
const iconMap: { [key: string]: string } = {
|
||||
pdf: "📕",
|
||||
doc: "📘",
|
||||
docx: "📘",
|
||||
xls: "📗",
|
||||
xlsx: "📗",
|
||||
ppt: "📙",
|
||||
pptx: "📙",
|
||||
txt: "📝",
|
||||
zip: "🗜️",
|
||||
rar: "🗜️",
|
||||
"7z": "🗜️",
|
||||
jpg: "🖼️",
|
||||
jpeg: "🖼️",
|
||||
png: "🖼️",
|
||||
gif: "🖼️",
|
||||
mp4: "🎬",
|
||||
avi: "🎬",
|
||||
mov: "🎬",
|
||||
mp3: "🎵",
|
||||
wav: "🎵",
|
||||
flac: "🎵",
|
||||
};
|
||||
fileIcon = iconMap[fileExtension] || "📄";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.fileMessage}>
|
||||
<div className={styles.fileCard}>
|
||||
<div className={styles.fileIcon}>{fileIcon}</div>
|
||||
<div className={styles.fileInfo}>
|
||||
<div className={styles.fileName}>
|
||||
{fileName.length > 20
|
||||
? fileName.substring(0, 20) + "..."
|
||||
: fileName}
|
||||
</div>
|
||||
<div
|
||||
className={styles.fileAction}
|
||||
onClick={() => {
|
||||
try {
|
||||
window.open(content, "_blank");
|
||||
} catch (e) {
|
||||
console.error("文件打开失败:", e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
点击查看
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return renderErrorMessage("[小程序/文件消息]");
|
||||
} catch (e) {
|
||||
console.warn("小程序/文件消息解析失败:", e);
|
||||
return renderErrorMessage("[小程序/文件消息 - 解析失败]");
|
||||
}
|
||||
return <SmallProgramMessage content={content || ""} />;
|
||||
|
||||
default: {
|
||||
// 兼容旧版本和未知消息类型的处理逻辑
|
||||
|
||||
Reference in New Issue
Block a user