# uni-app 小程序模板
基于 uni-app + Vue 3 + Pinia 的微信小程序模板,内置即时通讯功能。
## 技术栈
- uni-app (Vue 3 + Composition API)
- Pinia 状态管理
- SignalR WebSocket 实时通讯
- SCSS 样式
## 项目结构
```
├── api/ # 接口模块
│ ├── request.js # 请求封装(Token、重试、取消)
│ ├── auth.js # 认证接口
│ ├── user.js # 用户接口
│ ├── chat.js # 聊天接口
│ └── index.js # 统一导出
├── components/ # 公共组件
│ ├── Loading/ # 加载状态
│ ├── Empty/ # 空状态
│ ├── Popup/ # 弹窗
│ ├── EmojiPicker/ # 表情选择器
│ └── VoiceRecorder/ # 语音录制
├── config/
│ └── index.js # 环境配置
├── pages/ # 页面
│ ├── index/ # 首页
│ ├── message/ # 消息列表
│ ├── chat/ # 聊天页面
│ ├── mine/ # 我的
│ └── login/ # 登录
├── store/ # 状态管理
│ ├── user.js # 用户状态
│ ├── chat.js # 聊天状态
│ └── index.js # 统一导出
├── utils/ # 工具函数
│ ├── storage.js # 本地存储
│ ├── format.js # 格式化
│ ├── signalr.js # SignalR 客户端
│ └── emoji.js # 表情数据
└── static/ # 静态资源
```
## 快速开始
### 安装依赖
```bash
npm install
```
### 开发运行
```bash
npm run dev:mp-weixin
```
### 生产构建
```bash
npm run build:mp-weixin
```
## 配置说明
### 环境配置
编辑 `config/index.js`:
```javascript
const ENV = {
development: {
API_BASE_URL: 'http://localhost:5000/api',
STATIC_BASE_URL: 'http://localhost:5000',
SIGNALR_URL: 'ws://localhost:5000/hubs/chat'
},
production: {
API_BASE_URL: 'https://your-domain.com/api',
STATIC_BASE_URL: 'https://your-domain.com',
SIGNALR_URL: 'wss://your-domain.com/hubs/chat'
}
}
// 切换环境
const CURRENT_ENV = 'development'
```
### 请求配置
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| REQUEST_TIMEOUT | 30000 | 请求超时时间(ms) |
| REQUEST_RETRY_COUNT | 2 | 失败重试次数 |
| REQUEST_RETRY_DELAY | 1000 | 重试间隔(ms) |
## 核心模块
### 请求封装 (api/request.js)
```javascript
import { get, post, put, del } from '@/api/request'
// GET 请求
const res = await get('/users/detail', { userId: 1 })
// POST 请求
const res = await post('/chat/send', { content: 'hello' })
// 带配置的请求
const res = await get('/data', {}, {
needAuth: false, // 是否需要 Token
retry: false, // 是否重试
cancelPrevious: true // 取消之前的相同请求
})
```
特性:
- 自动携带 JWT Token
- 401 状态码自动清除登录态
- 请求失败自动重试
- 支持取消请求
### 状态管理
#### 用户状态 (store/user.js)
```javascript
import { useUserStore } from '@/store'
const userStore = useUserStore()
// 登录
userStore.login({ token, userInfo })
// 登出
userStore.logout()
// 判断登录状态
if (userStore.isLoggedIn) { ... }
// 从存储恢复
userStore.restoreFromStorage()
```
#### 聊天状态 (store/chat.js)
```javascript
import { useChatStore, MessageType, MessageStatus } from '@/store'
const chatStore = useChatStore()
// 设置会话列表
chatStore.setSessions(sessions)
// 发送消息
chatStore.sendMessage(sessionId, {
senderId: 1,
receiverId: 2,
messageType: MessageType.TEXT,
content: 'hello'
})
// 接收消息
chatStore.receiveMessage(message)
// 标记已读
chatStore.markSessionAsRead(sessionId)
```
消息类型:
```javascript
MessageType.TEXT // 1 - 文本
MessageType.VOICE // 2 - 语音
MessageType.IMAGE // 3 - 图片
```
消息状态:
```javascript
MessageStatus.SENDING // 0 - 发送中
MessageStatus.SENT // 1 - 已发送
MessageStatus.DELIVERED // 2 - 已送达
MessageStatus.READ // 3 - 已读
MessageStatus.FAILED // 4 - 失败
```
### SignalR 实时通讯 (utils/signalr.js)
```javascript
import signalR from '@/utils/signalr'
// 连接
await signalR.connect()
// 监听消息
signalR.on('ReceiveMessage', (message) => {
console.log('收到消息:', message)
})
// 加入会话
await signalR.joinSession(sessionId)
// 离开会话
await signalR.leaveSession(sessionId)
// 调用服务端方法
const result = await signalR.invoke('MethodName', arg1, arg2)
// 发送消息(不等待响应)
signalR.send('MethodName', arg1, arg2)
// 断开连接
signalR.disconnect()
```
特性:
- 自动重连(最多5次,指数退避)
- 心跳保活(15秒间隔)
- 支持方法调用和事件监听
### 本地存储 (utils/storage.js)
```javascript
import {
setToken, getToken, removeToken,
setUserInfo, getUserInfo, removeUserInfo,
setStorage, getStorage, removeStorage, clearStorage
} from '@/utils/storage'
// Token
setToken('xxx')
const token = getToken()
// 用户信息
setUserInfo({ userId: 1, nickname: '用户' })
const user = getUserInfo()
// 通用存储
setStorage('key', value)
const value = getStorage('key', defaultValue)
```
### 格式化工具 (utils/format.js)
```javascript
import { formatTimestamp, maskName, maskPhone } from '@/utils/format'
// 时间格式化
formatTimestamp('2024-01-15 10:30:00')
// 今天 10:30 / 昨天 / 前天 / 1月15号
// 姓名脱敏
maskName('张三') // 张*
maskName('张三丰') // 张*丰
// 手机脱敏
maskPhone('13812345678') // 138****5678
```
## 公共组件
### Loading
```vue
```
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| type | string | 'page' | page/more |
| loading | boolean | false | 加载状态 |
| noMore | boolean | false | 没有更多 |
### Empty
```vue
```
### Popup
```vue
```
### EmojiPicker
```vue
```
### VoiceRecorder
```vue
// 回调数据
{
tempFilePath: '临时文件路径',
duration: 10 // 秒
}
```
## 页面开发
### 添加新页面
1. 在 `pages/` 下创建页面目录和 vue 文件
2. 在 `pages.json` 中注册页面:
```json
{
"pages": [
{
"path": "pages/new-page/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "新页面"
}
}
]
}
```
### 页面模板
```vue
页面标题
```
## 后端接口约定
### 响应格式
```json
{
"code": 0,
"message": "success",
"data": {}
}
```
- code = 0 表示成功
- code != 0 表示失败,message 为错误信息
### 认证接口
```
POST /auth/login
Request: { code: "微信登录code" }
Response: {
token: "JWT Token",
userId: 1,
nickname: "用户名",
avatar: "头像URL"
}
```
### 聊天接口
```
GET /chat/sessions
Response: {
items: [{
sessionId: 1,
otherUserId: 2,
otherNickname: "对方昵称",
otherAvatar: "对方头像",
lastMessageContent: "最后消息",
lastMessageTime: "2024-01-15T10:30:00",
unreadCount: 5
}]
}
GET /chat/messages?sessionId=1&pageIndex=1&pageSize=20
Response: {
items: [{
messageId: 1,
senderId: 1,
receiverId: 2,
messageType: 1,
content: "消息内容",
createTime: "2024-01-15T10:30:00",
isSelf: true
}]
}
POST /chat/send
Request: {
sessionId: 1,
receiverId: 2,
messageType: 1,
content: "消息内容"
}
```
### SignalR Hub
Hub 地址: `/hubs/chat`
服务端方法:
- `JoinSession(sessionId)` - 加入会话
- `LeaveSession(sessionId)` - 离开会话
客户端事件:
- `ReceiveMessage` - 接收新消息
## License
MIT