mi-assessment/uniapp/模板_README.md
2026-02-09 14:45:06 +08:00

8.8 KiB
Raw Blame History

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/                 # 静态资源

快速开始

安装依赖

npm install

开发运行

npm run dev:mp-weixin

生产构建

npm run build:mp-weixin

配置说明

环境配置

编辑 config/index.js

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)

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)

import { useUserStore } from '@/store'

const userStore = useUserStore()

// 登录
userStore.login({ token, userInfo })

// 登出
userStore.logout()

// 判断登录状态
if (userStore.isLoggedIn) { ... }

// 从存储恢复
userStore.restoreFromStorage()

聊天状态 (store/chat.js)

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)

消息类型:

MessageType.TEXT   // 1 - 文本
MessageType.VOICE  // 2 - 语音
MessageType.IMAGE  // 3 - 图片

消息状态:

MessageStatus.SENDING   // 0 - 发送中
MessageStatus.SENT      // 1 - 已发送
MessageStatus.DELIVERED // 2 - 已送达
MessageStatus.READ      // 3 - 已读
MessageStatus.FAILED    // 4 - 失败

SignalR 实时通讯 (utils/signalr.js)

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)

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)

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

<Loading type="page" :loading="true" />
<Loading type="more" :loading="true" :noMore="false" />
属性 类型 默认值 说明
type string 'page' page/more
loading boolean false 加载状态
noMore boolean false 没有更多

Empty

<Empty text="暂无数据" :showButton="true" buttonText="去看看" @click="handleClick" />

Popup

<Popup 
  :visible="show"
  title="提示"
  content="确定要执行此操作吗?"
  buttonText="确定"
  :showCancel="true"
  @close="show = false"
  @confirm="handleConfirm"
/>

EmojiPicker

<EmojiPicker 
  :visible="showEmoji"
  @close="showEmoji = false"
  @select="handleEmojiSelect"
/>

VoiceRecorder

<VoiceRecorder @send="handleSendVoice" />

// 回调数据
{
  tempFilePath: '临时文件路径',
  duration: 10  // 秒
}

页面开发

添加新页面

  1. pages/ 下创建页面目录和 vue 文件
  2. pages.json 中注册页面:
{
  "pages": [
    {
      "path": "pages/new-page/index",
      "style": {
        "navigationStyle": "custom",
        "navigationBarTitleText": "新页面"
      }
    }
  ]
}

页面模板

<template>
  <view class="page">
    <!-- 自定义导航栏 -->
    <view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
      <view class="navbar-content">
        <text class="title">页面标题</text>
      </view>
    </view>

    <!-- 内容区域 -->
    <view class="content" :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">
      <!-- 页面内容 -->
    </view>
  </view>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const statusBarHeight = ref(20)

onMounted(() => {
  uni.getSystemInfo({
    success: (res) => {
      statusBarHeight.value = res.statusBarHeight || 20
    }
  })
})
</script>

后端接口约定

响应格式

{
  "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