feat:聊天信息支持 文件、图片、语音发送,增加一键启停脚本

This commit is contained in:
bujinyuan
2026-04-20 11:53:27 +08:00
parent 1a5aac9998
commit 0c1540bdee
116 changed files with 20277 additions and 144 deletions

View File

@@ -0,0 +1,704 @@
# Phase 2d 设计文档:消息类型扩展
> **状态:** ✅ 已完成
> **分支:** `feature/phase2d-message-types`
> **前置依赖:** Phase 2c 全部完成(群聊与已读回执)
> **最后更新:** 2026-03-03
---
## 一、设计目标
基于 Phase 2b/2c 的单聊+群聊文本消息基础设施,扩展三种富媒体消息类型和管理端消息管理功能。
**核心交付物:**
- 图片消息type=2前端压缩+多图发送最多9张、后端生成 JPEG 缩略图、聊天内缩略图展示+大图预览
- 语音消息type=3微信风格按住录音/松手发送/上滑取消、最长60秒、时长波形条展示+播放
- 文件消息type=5任意文件上传最大50MB、类型图标+大小展示、点击下载+在线预览PDF/图片)
- 文件上传服务增强大小限制提升至50MB、图片专用上传缩略图生成、语音专用上传格式+时长校验)
- 管理端消息管理:消息列表(完整筛选搜索)+ 消息统计仪表板(趋势图+类型分布+活跃排行)+ 敏感消息删除/撤回
**不包含(留待后续阶段):**
- 视频消息type=4录制/压缩/缩略图复杂度高)
- 表情包 / 自定义贴纸
- 消息转发 / 合并转发
- 消息引用 / 回复
---
## 二、需求决策记录
### 2.1 消息类型决策
| 决策项 | 选择 | 理由 |
|--------|------|------|
| 消息类型范围 | 图片+语音+文件(无视频) | 视频录制/压缩/缩略图复杂度高,留后续阶段 |
| 多图策略 | 单条消息包含多图最多9张 | 微信风格extra JSON 存图片列表 |
| 图片缩略图 | 后端 Go 图片库生成 | 保证各端一致性,前端仅做发送前压缩 |
| 图片格式 | 统一转 JPEG | 兼容性最好,体积适中 |
| 图片压缩 | 前端发送前压缩(宽>1920px 等比缩放质量80% | 减少上传流量 |
| 缩略图尺寸 | 宽度不超过 200px等比缩放JPEG quality 80 | 列表加载快,质量可接受 |
### 2.2 语音消息决策
| 决策项 | 选择 | 理由 |
|--------|------|------|
| 交互方式 | 微信风格:按住录音/松手发送/上滑取消 | 用户认知成本低 |
| 录音时长 | 最长 60 秒 | 微信标准 |
| 录音格式 | MP3 | 跨端兼容性最好 |
| 录音技术 | uni-app `uni.getRecorderManager()` | uni-app 原生 API多端兼容 |
### 2.3 文件消息决策
| 决策项 | 选择 | 理由 |
|--------|------|------|
| 大小限制 | 50MB上调原 10MB | 支持 PPT/中等大小文件 |
| 文件能力 | 上传+类型图标+大小显示+下载+在线预览PDF/图片) | 完整用户体验 |
| 文件类型限制 | 不限制(后端仅做大小校验) | 通用文件传输场景 |
### 2.4 上传与发送流程决策
| 决策项 | 选择 | 理由 |
|--------|------|------|
| 上传流程 | 先上传后发送:前端 REST 上传 → 获取 URL → WS 发消息 | 简单可靠,解耦文件上传和消息发送 |
| 上传接口 | 按类型分离:通用/图片/语音三个接口 | 图片需要缩略图、语音需要时长校验,逻辑各异 |
### 2.5 UI/UX 决策
| 决策项 | 选择 | 理由 |
|--------|------|------|
| 会话列表预览 | 方括号标记:`[图片]``[语音 12"]``[文件] report.pdf` | 微信风格,用户习惯 |
| 图片展示 | 聊天内缩略图(网格)+ 点击大图预览Swiper | 标准 IM 体验 |
| 语音展示 | 时长+波形条+播放按钮 | 微信风格 |
| 文件展示 | 文件图标+文件名+大小+下载/预览按钮 | 清晰直观 |
| 表情包/贴纸 | 不做 | 专注核心媒体消息 |
### 2.6 管理端决策
| 决策项 | 选择 | 理由 |
|--------|------|------|
| 搜索筛选 | 完整:关键词+类型+发送者+会话/群+时间+状态 | 满足运营需求 |
| 统计维度 | 完整:趋势图+类型分布+活跃用户排行+群活跃度 | 数据运营可视化 |
| 图表库 | ECharts | admin 项目基于 Vue 3 + Element PlusECharts 集成良好 |
---
## 三、架构设计
### 3.1 模块目录结构
```
backend/go-service/
├── app/
│ ├── file/ # 文件上传模块(增强)
│ │ ├── controller/
│ │ │ └── file_controller.go # 新增 UploadImage / UploadVoice 接口
│ │ ├── service/
│ │ │ └── file_service.go # 新增 UploadWithThumbnail / UploadVoice 方法
│ │ ├── router.go # 新增 /upload/image 和 /upload/voice 路由
│ │ └── provider.go
│ ├── im/ # 即时通讯模块(适配)
│ │ ├── service/
│ │ │ └── im_service.go # SendMessage 适配 extra + 预览文案
│ │ └── handler/
│ │ └── event_handler.go # im.message.send 适配 extra 字段
│ ├── admin/ # 管理端(新增消息管理)
│ │ ├── controller/
│ │ │ └── message_manage_controller.go # [新增]
│ │ ├── service/
│ │ │ └── message_manage_service.go # [新增]
│ │ ├── dao/
│ │ │ └── message_manage_dao.go # [新增]
│ │ └── router.go # 注册消息管理路由
│ ├── dto/
│ │ ├── im_dto.go # 扩展 SendMessageRequest + MessageDTO 的 extra
│ │ └── admin_dto.go # 新增管理端消息 DTO
│ └── constants/
│ └── im.go # 消息类型常量注释更新2/3/5 不再标注"预留"
├── frontend/src/
│ ├── api/
│ │ └── file.js # [新增] 文件上传 API 封装
│ ├── components/
│ │ ├── msg/ # [新增] 消息类型组件
│ │ │ ├── MsgText.vue # 文本消息组件
│ │ │ ├── MsgImage.vue # 图片消息组件(网格+大图预览)
│ │ │ ├── MsgVoice.vue # 语音消息组件(波形条+播放)
│ │ │ └── MsgFile.vue # 文件消息组件(卡片+下载)
│ │ └── chat/ # [新增] 聊天辅助组件
│ │ ├── MorePanel.vue # "+"展开面板(图片/文件入口)
│ │ ├── VoiceRecorder.vue # 语音录制组件
│ │ └── ImagePreviewer.vue # 图片全屏预览
│ ├── pages/
│ │ ├── chat/conversation.vue # [修改] 引入消息组件+输入栏改造
│ │ └── group/conversation.vue # [修改] 同上
│ ├── store/
│ │ └── chat.js # [修改] 扩展发送方法
│ └── utils/
│ └── file.js # [新增] 文件工具(压缩/格式判断/大小格式化)
├── admin/src/
│ ├── api/
│ │ └── message.js # [新增] 管理端消息 API
│ ├── views/
│ │ └── message/ # [新增] 消息管理模块
│ │ ├── list.vue # 消息列表(筛选+分页+操作)
│ │ └── stats.vue # 消息统计仪表板
│ └── router/
│ └── index.js # 注册消息管理路由
```
### 3.2 跨模块依赖关系
```
file.FileService ← im.IMService (通过接口注入,暂不直接依赖)
im.IMService → convDAO/msgDAO (存储消息 + extra)
im.EventHandler → im.IMService (WS 事件调用)
admin.MessageManageService → im.MessageDAO (查询消息)
→ auth.UserDAO (查询发送者)
```
消息发送流程中,**文件上传与消息发送是解耦的**
1. 前端通过 REST API 上传文件到 MinIO获取 URL
2. 前端通过 WS 发送消息payload 中携带 `extra` 字段(含文件 URL
3. 后端 IM Service 直接存储,不再访问 file 模块
---
## 四、消息 extra 字段 JSON 结构设计
`im_messages.extra` 字段类型为 JSONB已存在按消息类型存储不同元数据。
### 4.1 图片消息type=2
```json
{
"images": [
{
"url": "http://localhost:9000/echochat/uploads/2026/03/05/abc.jpg",
"thumbnail_url": "http://localhost:9000/echochat/uploads/2026/03/05/abc_thumb.jpg",
"width": 1920,
"height": 1080,
"size": 245760,
"file_name": "photo.jpg"
},
{
"url": "http://localhost:9000/echochat/uploads/2026/03/05/def.jpg",
"thumbnail_url": "http://localhost:9000/echochat/uploads/2026/03/05/def_thumb.jpg",
"width": 1280,
"height": 720,
"size": 180000,
"file_name": "photo2.jpg"
}
]
}
```
### 4.2 语音消息type=3
```json
{
"voice": {
"url": "http://localhost:9000/echochat/uploads/2026/03/05/voice_xxx.mp3",
"duration": 12,
"size": 48000,
"file_name": "voice_20260305_120000.mp3"
}
}
```
### 4.3 文件消息type=5
```json
{
"file": {
"url": "http://localhost:9000/echochat/uploads/2026/03/05/doc_xxx.pdf",
"file_name": "年度报告.pdf",
"size": 1048576,
"mime_type": "application/pdf",
"ext": ".pdf"
}
}
```
---
## 五、API 设计
### 5.1 文件上传 REST API扩展
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| POST | /api/v1/upload | 需认证 | 通用文件上传最大50MB |
| POST | /api/v1/upload/image | 需认证 | 图片上传(压缩+生成缩略图,返回双 URL |
| POST | /api/v1/upload/voice | 需认证 | 语音上传(校验格式+时长<=60s |
#### POST /api/v1/upload/image
**请求**multipart/form-data字段名 `file`
**成功响应**
```json
{
"code": 0,
"message": "success",
"data": {
"url": "http://localhost:9000/echochat/uploads/2026/03/05/xxx.jpg",
"thumbnail_url": "http://localhost:9000/echochat/uploads/2026/03/05/xxx_thumb.jpg",
"file_name": "photo.jpg",
"size": 245760,
"width": 1920,
"height": 1080,
"mime_type": "image/jpeg"
}
}
```
#### POST /api/v1/upload/voice
**请求**multipart/form-data字段名 `file`
**成功响应**
```json
{
"code": 0,
"message": "success",
"data": {
"url": "http://localhost:9000/echochat/uploads/2026/03/05/voice_xxx.mp3",
"file_name": "voice_20260305.mp3",
"size": 48000,
"duration": 12,
"mime_type": "audio/mpeg"
}
}
```
### 5.2 管理端消息管理 REST API新增
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/v1/admin/messages | admin | 消息列表(分页+多条件筛选) |
| GET | /api/v1/admin/messages/:id | admin | 消息详情(含发送者信息) |
| DELETE | /api/v1/admin/messages/:id | admin | 删除消息 |
| PUT | /api/v1/admin/messages/:id/recall | admin | 管理员撤回消息 |
| GET | /api/v1/admin/messages/stats | admin | 消息统计数据 |
#### GET /api/v1/admin/messages
**查询参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| keyword | string | 搜索关键词(模糊匹配 content |
| type | int | 消息类型筛选1/2/3/5/10 |
| sender_id | int64 | 发送者 ID |
| conversation_id | int64 | 会话 ID |
| status | int | 消息状态1=正常/2=已撤回/3=已删除) |
| start_time | string | 开始时间YYYY-MM-DD |
| end_time | string | 结束时间YYYY-MM-DD |
| page | int | 页码默认1 |
| page_size | int | 每页条数默认20 |
**成功响应**
```json
{
"code": 0,
"message": "success",
"data": {
"list": [
{
"id": 1,
"conversation_id": 5,
"sender_id": 4,
"sender_nickname": "张三",
"sender_avatar": "",
"type": 2,
"type_label": "图片",
"content": "",
"extra": { "images": [...] },
"status": 1,
"status_label": "正常",
"created_at": "2026-03-05 10:30:00"
}
],
"total": 1000,
"page": 1,
"page_size": 20
}
}
```
#### GET /api/v1/admin/messages/stats
**查询参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| days | int | 统计天数默认7最大90 |
**成功响应**
```json
{
"code": 0,
"message": "success",
"data": {
"total_count": 12580,
"today_count": 350,
"type_distribution": [
{ "type": 1, "label": "文本", "count": 10000 },
{ "type": 2, "label": "图片", "count": 1500 },
{ "type": 3, "label": "语音", "count": 500 },
{ "type": 5, "label": "文件", "count": 300 },
{ "type": 10, "label": "系统消息", "count": 280 }
],
"daily_trend": [
{ "date": "2026-03-01", "count": 280 },
{ "date": "2026-03-02", "count": 320 }
],
"active_users": [
{ "user_id": 4, "nickname": "张三", "count": 120 }
],
"active_groups": [
{ "group_id": 1, "name": "技术讨论群", "count": 500 }
]
}
}
```
### 5.3 WebSocket 事件变更
**`im.message.send` 请求扩展**
```json
{
"event": "im.message.send",
"data": {
"conversation_id": 8,
"type": 2,
"content": "",
"client_msg_id": "xxx",
"extra": "{\"images\":[{\"url\":\"...\",\"thumbnail_url\":\"...\",\"width\":1920,\"height\":1080,\"size\":245760,\"file_name\":\"photo.jpg\"}]}"
}
}
```
**`im.message.new` 推送扩展**
推送数据增加 `extra` 字段JSON string前端解析后根据 `type` 渲染对应组件。
**`im.message.send.ack` 响应扩展**
ACK 的 `data`MessageDTO增加 `extra` 字段。
---
## 六、核心业务流程
### 6.1 图片消息发送流程
1. 用户点击"+"面板中的"图片",调用 `uni.chooseImage` 选择图片最多9张
2. 前端对每张图片进行压缩(宽>1920px 等比缩放质量80%
3. 逐张调用 `POST /api/v1/upload/image` 上传
4. 后端接收图片 → 生成 JPEG 缩略图宽200pxquality 80→ 原图+缩略图存 MinIO
5. 返回 `{ url, thumbnail_url, width, height, size, file_name, mime_type }`
6. 所有图片上传完成后,前端组装 `extra.images` 数组
7. 通过 WS 发送 `im.message.send``type=2``content=""``extra` 为 JSON 字符串
8. 后端存储消息,`UpdateLastMessage` 预览文案为 `[图片]``[图片x3]`
9. 推送 `im.message.new` 给接收方,携带 `extra`
### 6.2 语音消息发送流程
1. 用户长按麦克风按钮,调用 `uni.getRecorderManager().start({ format: 'mp3', duration: 60000 })`
2. 录音过程中显示录音动画(时长 + 波形),上滑显示"取消发送"
3. 松手停止录音 → 录音回调返回临时文件路径
4. 时长 < 1秒提示"录音时间太短"并丢弃
5. 调用 `POST /api/v1/upload/voice` 上传语音文件
6. 后端校验格式和时长<=60s)→ MinIO 返回 `{ url, duration, size, file_name }`
7. 前端组装 `extra.voice` 对象
8. 通过 WS 发送`type=3``content=""``extra` JSON 字符串
9. 后端存储预览文案 `[语音 12"]`
### 6.3 文件消息发送流程
1. 用户点击"+"面板中的"文件"调用 `uni.chooseFile` `uni.chooseMessageFile`
2. 校验文件大小<=50MB
3. 调用 `POST /api/v1/upload` 通用上传接口
4. 返回 `{ url, file_name, size }`
5. 前端补充 `mime_type` `ext`组装 `extra.file` 对象
6. 通过 WS 发送`type=5``content=""``extra` JSON 字符串
7. 后端存储预览文案 `[文件] report.pdf`
### 6.4 管理员撤回消息流程
1. 管理员在消息列表选中消息 点击"撤回"
2. 调用 `PUT /api/v1/admin/messages/:id/recall`
3. 后端将消息 status 改为 2已撤回
4. 推送 `im.message.recalled` 给相关在线用户
5. 会话最后消息预览更新为"管理员撤回了一条消息"
---
## 七、会话列表预览文案规则
`IMService` `UpdateLastMessage` 时根据消息类型生成预览文案
| type | content 字段用途 | last_msg_content 预览 |
|------|-----------------|----------------------|
| 1文本 | 消息正文 | 消息正文截断 |
| 2图片 | 空字符串 | `[图片]` / `[图片x3]`多图标数量 |
| 3语音 | 空字符串 | `[语音 12"]`duration extra 解析 |
| 5文件 | 空字符串 | `[文件] report.pdf`file_name extra 解析 |
| 10系统 | 系统消息正文 | 系统消息正文 |
---
## 八、前端页面规划
### 8.1 新增组件(前台用户端)
| 组件 | 路径 | 说明 |
|------|------|------|
| MsgText.vue | components/msg/ | 文本消息组件 conversation 提取 |
| MsgImage.vue | components/msg/ | 图片消息1张=大图2-4张=2列网格5-9张=3列网格点击放大 |
| MsgVoice.vue | components/msg/ | 语音消息播放按钮+波形条+时长宽度按时长缩放 |
| MsgFile.vue | components/msg/ | 文件消息文件图标+文件名+大小+下载/预览按钮 |
| MorePanel.vue | components/chat/ | 输入栏"+"展开面板图片文件两个入口 |
| VoiceRecorder.vue | components/chat/ | 语音录制长按录音+上滑取消+时长显示+波形动画 |
| ImagePreviewer.vue | components/chat/ | 图片全屏预览Swiper 左右滑+双指缩放 |
### 8.2 修改页面(前台用户端)
| 页面 | 修改内容 |
|------|----------|
| pages/chat/conversation.vue | 消息列表按 type 渲染不同组件输入栏增加"+"和麦克风按钮 |
| pages/group/conversation.vue | 同上 |
| store/chat.js | 扩展 sendMessage 支持 extra新增 sendImageMessage/sendVoiceMessage/sendFileMessage |
### 8.3 新增页面(管理端)
| 页面 | 路径 | 说明 |
|------|------|------|
| 消息列表 | views/message/list.vue | 多条件筛选+分页表格+撤回/删除操作 |
| 消息统计 | views/message/stats.vue | ECharts 图表趋势折线图+类型饼图+活跃排行柱状图 |
---
## 九、DTO 扩展规划
### 9.1 发送消息请求扩展
```go
// SendMessageRequest 发送消息请求WS 事件 im.message.send 的 data 字段)
type SendMessageRequest struct {
ConversationID int64 `json:"conversation_id"`
TargetUserID int64 `json:"target_user_id"`
Type int `json:"type"`
Content string `json:"content"`
ClientMsgID string `json:"client_msg_id"`
AtUserIDs []int64 `json:"at_user_ids"`
Extra string `json:"extra"` // [Phase 2d 新增] 扩展数据 JSON 字符串
}
```
### 9.2 消息 DTO 扩展
```go
// MessageDTO 消息传输对象(返回给前端)
type MessageDTO struct {
ID int64 `json:"id"`
ConversationID int64 `json:"conversation_id"`
SenderID int64 `json:"sender_id"`
Type int `json:"type"`
Content string `json:"content"`
Extra *string `json:"extra,omitempty"` // [Phase 2d 新增] 扩展数据 JSON
Status int `json:"status"`
ClientMsgID string `json:"client_msg_id"`
AtUserIDs []int64 `json:"at_user_ids"`
CreatedAt string `json:"created_at"`
}
```
### 9.3 文件上传结果 DTO 扩展
```go
// ImageUploadResult 图片上传结果
type ImageUploadResult struct {
URL string `json:"url"`
ThumbnailURL string `json:"thumbnail_url"`
FileName string `json:"file_name"`
Size int64 `json:"size"`
Width int `json:"width"`
Height int `json:"height"`
MimeType string `json:"mime_type"`
}
// VoiceUploadResult 语音上传结果
type VoiceUploadResult struct {
URL string `json:"url"`
FileName string `json:"file_name"`
Size int64 `json:"size"`
Duration int `json:"duration"`
MimeType string `json:"mime_type"`
}
```
### 9.4 管理端消息 DTO
```go
// AdminMessageListRequest 管理端消息列表查询
type AdminMessageListRequest struct {
Keyword string `form:"keyword"`
Type *int `form:"type"`
SenderID *int64 `form:"sender_id"`
ConversationID *int64 `form:"conversation_id"`
Status *int `form:"status"`
StartTime string `form:"start_time"`
EndTime string `form:"end_time"`
Page int `form:"page"`
PageSize int `form:"page_size"`
}
// AdminMessageDTO 管理端消息条目
type AdminMessageDTO struct {
ID int64 `json:"id"`
ConversationID int64 `json:"conversation_id"`
SenderID int64 `json:"sender_id"`
SenderNickname string `json:"sender_nickname"`
SenderAvatar string `json:"sender_avatar"`
Type int `json:"type"`
TypeLabel string `json:"type_label"`
Content string `json:"content"`
Extra *string `json:"extra,omitempty"`
Status int `json:"status"`
StatusLabel string `json:"status_label"`
CreatedAt string `json:"created_at"`
}
// AdminMessageStatsRequest 管理端消息统计请求
type AdminMessageStatsRequest struct {
Days int `form:"days"` // 统计天数默认7最大90
}
// AdminMessageStatsResponse 管理端消息统计响应
type AdminMessageStatsResponse struct {
TotalCount int64 `json:"total_count"`
TodayCount int64 `json:"today_count"`
TypeDistribution []TypeDistItem `json:"type_distribution"`
DailyTrend []DailyTrendItem `json:"daily_trend"`
ActiveUsers []ActiveUserItem `json:"active_users"`
ActiveGroups []ActiveGroupItem `json:"active_groups"`
}
type TypeDistItem struct {
Type int `json:"type"`
Label string `json:"label"`
Count int64 `json:"count"`
}
type DailyTrendItem struct {
Date string `json:"date"`
Count int64 `json:"count"`
}
type ActiveUserItem struct {
UserID int64 `json:"user_id"`
Nickname string `json:"nickname"`
Count int64 `json:"count"`
}
type ActiveGroupItem struct {
GroupID int64 `json:"group_id"`
Name string `json:"name"`
Count int64 `json:"count"`
}
```
---
## 十、技术要点
### 10.1 Go 缩略图生成
- 依赖`github.com/disintegration/imaging`
- 流程`imaging.Open(file)` `imaging.Resize(img, 200, 0, imaging.Lanczos)` `imaging.Encode(buf, thumb, imaging.JPEG, imaging.JPEGQuality(80))`
- 缩略图文件名原始 UUID + `_thumb` 后缀 `abc123_thumb.jpg`
- 原图同时进行 JPEG 转码如果不是 JPEG 格式
### 10.2 前端图片压缩
- H5 平台使用 Canvas 压缩创建 Canvas drawImage toBlob('image/jpeg', 0.8)
- 小程序/App 平台使用 `uni.compressImage({ src, quality: 80 })`
- 压缩策略宽度>1920px 等比缩放至 1920px质量 80%
### 10.3 语音录制
- uni-app RecorderManager API
- `start({ format: 'mp3', duration: 60000, sampleRate: 44100, numberOfChannels: 1 })`
- 监听 `onStop` 获取录音文件临时路径
- 时长计算:回调中的 `duration` 字段(毫秒),前端转秒显示
### 10.4 语音播放
- uni-app InnerAudioContext API
- `uni.createInnerAudioContext()``src = voiceUrl``play()`
- 全局单例:同一时间只允许一个语音播放
- 播放状态:空闲 → 加载中 → 播放中 → 播放完成
### 10.5 文件下载与预览
- 下载:`uni.downloadFile({ url })``uni.saveFile()` 或直接 `window.open(url)` (H5)
- 预览PDF 和图片通过 `uni.openDocument` 或浏览器新标签打开
- 文件类型图标映射根据扩展名匹配图标PDF/Word/Excel/PPT/ZIP/图片/音频/通用)
### 10.6 管理端 ECharts 集成
- `npm install echarts` 安装
- 使用 Vue 3 Composition API + `onMounted` 初始化
- 响应式:`window.resize``chart.resize()`
- 主题色与系统保持一致Primary #2563EB
---
## 十一、补充设计细节
### 11.1 消息内容安全
- 文件上传不做内容安全审查(留后续阶段)
- 管理端提供人工审查和撤回能力
- 上传文件类型不限制,但大小限制 50MB
### 11.2 多图布局规则
| 图片数量 | 布局 | 单图最大宽度 |
|----------|------|-------------|
| 1 张 | 自适应宽度(最大 500rpx | 500rpx |
| 2 张 | 2列1行 | 240rpx |
| 3 张 | 3列1行 | 160rpx |
| 4 张 | 2列2行 | 240rpx |
| 5-6 张 | 3列2行 | 160rpx |
| 7-9 张 | 3列3行 | 160rpx |
### 11.3 文件类型图标映射
| 扩展名 | 图标 | 颜色 |
|--------|------|------|
| .pdf | PDF 图标 | #E53E3E |
| .doc/.docx | Word 图标 | #2B6CB0 |
| .xls/.xlsx | Excel 图标 | #276749 |
| .ppt/.pptx | PPT 图标 | #C05621 |
| .zip/.rar/.7z | 压缩包图标 | #6B46C1 |
| .jpg/.png/.gif | 图片图标 | #38A169 |
| .mp3/.wav/.aac | 音频图标 | #D69E2E |
| 其他 | 通用文件图标 | #718096 |
### 11.4 语音波形条设计
- 宽度:按时长线性缩放,最小 120rpx1秒最大 500rpx60秒
- 高度:固定 60rpx
- 波形:使用 CSS 模拟随机高度的竖条5-6 个竖条)
- 播放时:从左到右逐条变色动画