Uniapp社交类APP开发全流程 | 跨平台应用实战指南

2025-08-12 0 533

一、项目规划与技术选型

本教程将使用Uniapp开发一个完整的社交类应用,支持微信小程序、Android和iOS三端发布。

核心功能模块:

  • 用户认证系统(手机号+验证码)
  • 朋友圈动态发布与浏览
  • 即时通讯(文字+图片)
  • 地理位置社交
  • 用户个人主页
  • 消息通知系统

技术架构:

  1. 前端框架:Uniapp 3.0 + Vue2
  2. UI组件库:uView UI 2.0
  3. 状态管理:Vuex
  4. 即时通讯:Socket.io + WebSocket
  5. 地图服务:腾讯位置服务
  6. 后端接口:RESTful API

二、项目初始化与工程配置

1. 创建Uniapp项目

# 通过HBuilderX创建项目
文件 → 新建 → 项目 → Uniapp项目
选择默认模板,勾选Vue2版本

# 或者使用CLI方式
vue create -p dcloudio/uni-preset-vue social-app
cd social-app

# 安装必要依赖
npm install uview-ui socket.io-client qs

2. 项目目录结构优化

social-app/
├── common/            # 公共资源
│   ├── icons/         # 图标资源
│   └── styles/        # 公共样式
├── components/        # 组件目录
│   ├── base/          # 基础组件
│   └── business/      # 业务组件
├── pages/             # 页面目录
│   ├── home/          # 首页
│   ├── social/        # 社交功能
│   └── user/          # 用户中心
├── static/            # 静态资源
├── store/             # Vuex状态管理
├── utils/             # 工具函数
│   ├── api/           # 接口封装
│   ├── libs/          # 第三方库
│   └── helpers/       # 辅助函数
├── App.vue            # 应用入口
├── main.js            # 主入口文件
└── manifest.json      # 应用配置

3. 基础配置 (main.js)

import Vue from 'vue'
import App from './App'
import uView from 'uview-ui'
import store from './store'

Vue.use(uView)
Vue.prototype.$store = store

// 全局挂载常用方法
import { http } from '@/utils/api'
Vue.prototype.$http = http

// 开发环境启用调试
if (process.env.NODE_ENV === 'development') {
  Vue.config.productionTip = true
} else {
  Vue.config.productionTip = false
}

const app = new Vue({
  store,
  ...App
})
app.$mount()

三、核心功能实现

1. 用户认证系统

实现手机号登录组件:

<template>
  <view class="login-container">
    <u-form :model="form" :rules="rules" ref="loginForm">
      <u-form-item prop="phone">
        <u-input v-model="form.phone" placeholder="请输入手机号" />
      </u-form-item>
      
      <u-form-item prop="code">
        <u-input v-model="form.code" placeholder="请输入验证码" />
        <u-button 
          slot="right" 
          @click="getSmsCode" 
          :disabled="codeDisabled">
          {{ codeText }}
        </u-button>
      </u-form-item>
    </u-form>
    
    <u-button 
      type="primary" 
      @click="handleLogin" 
      :loading="loading">
      登录
    </u-button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      form: {
        phone: '',
        code: ''
      },
      rules: {
        phone: [
          { required: true, message: '请输入手机号', trigger: 'blur' },
          { pattern: /^1[3-9]d{9}$/, message: '手机号格式不正确' }
        ],
        code: [
          { required: true, message: '请输入验证码', trigger: 'blur' },
          { pattern: /^d{6}$/, message: '验证码格式不正确' }
        ]
      },
      codeText: '获取验证码',
      codeDisabled: false,
      loading: false
    }
  },
  
  methods: {
    async getSmsCode() {
      if (!this.form.phone) {
        return this.$u.toast('请输入手机号')
      }
      
      let countdown = 60
      this.codeDisabled = true
      this.codeText = `${countdown}s后重新获取`
      
      const timer = setInterval(() => {
        countdown--
        this.codeText = `${countdown}s后重新获取`
        if (countdown <= 0) {
          clearInterval(timer)
          this.codeText = '获取验证码'
          this.codeDisabled = false
        }
      }, 1000)
      
      try {
        await this.$http.post('/sms/send', { phone: this.form.phone })
        this.$u.toast('验证码已发送')
      } catch (e) {
        clearInterval(timer)
        this.codeText = '获取验证码'
        this.codeDisabled = false
      }
    },
    
    async handleLogin() {
      try {
        this.loading = true
        const res = await this.$http.post('/auth/login', this.form)
        
        // 存储用户token
        uni.setStorageSync('token', res.data.token)
        
        // 更新Vuex状态
        this.$store.commit('user/SET_USER_INFO', res.data.userInfo)
        
        // 跳转到首页
        uni.reLaunch({ url: '/pages/home/index' })
      } finally {
        this.loading = false
      }
    }
  }
}
</script>

2. 朋友圈动态功能

<template>
  <view class="moment-container">
    <scroll-view 
      scroll-y 
      @scrolltolower="loadMore"
      refresher-enabled
      @refresherrefresh="refreshData">
      
      <moment-post 
        v-for="item in momentList" 
        :key="item.id" 
        :data="item"
        @like="handleLike"
        @comment="handleComment" />
      
      <u-loadmore 
        :status="loadingStatus" 
        :load-text="loadText" />
    </scroll-view>
    
    <u-button 
      class="post-btn" 
      type="primary" 
      shape="circle"
      @click="gotoPost">
      <u-icon name="plus"></u-icon>
    </u-button>
  </view>
</template>

<script>
import MomentPost from '@/components/business/MomentPost'

export default {
  components: { MomentPost },
  
  data() {
    return {
      momentList: [],
      page: 1,
      pageSize: 10,
      loadingStatus: 'loadmore',
      loadText: {
        loadmore: '上拉加载更多',
        loading: '正在加载...',
        nomore: '没有更多了'
      }
    }
  },
  
  onLoad() {
    this.loadData()
  },
  
  methods: {
    async loadData() {
      this.loadingStatus = 'loading'
      
      try {
        const res = await this.$http.get('/moment/list', {
          params: { page: this.page, pageSize: this.pageSize }
        })
        
        if (this.page === 1) {
          this.momentList = res.data.list
        } else {
          this.momentList = [...this.momentList, ...res.data.list]
        }
        
        this.loadingStatus = res.data.list.length  {
        uni.stopPullDownRefresh()
      }, 1000)
    },
    
    loadMore() {
      if (this.loadingStatus === 'nomore') return
      this.page++
      this.loadData()
    },
    
    handleLike(momentId) {
      // 处理点赞逻辑
    },
    
    handleComment(momentId) {
      // 处理评论逻辑
    },
    
    gotoPost() {
      uni.navigateTo({ url: '/pages/social/post' })
    }
  }
}
</script>

四、即时通讯实现

1. WebSocket连接管理

// utils/libs/socket.js
import io from 'socket.io-client'
import { getToken } from '@/utils/auth'

let socket = null
const eventCallbacks = {}

export const initSocket = () => {
  if (socket) return socket
  
  socket = io('https://your-websocket-server.com', {
    transports: ['websocket'],
    query: { token: getToken() }
  })
  
  socket.on('connect', () => {
    console.log('Socket connected')
  })
  
  socket.on('disconnect', () => {
    console.log('Socket disconnected')
  })
  
  socket.on('error', (err) => {
    console.error('Socket error:', err)
  })
  
  // 通用消息处理
  socket.on('message', (data) => {
    const callback = eventCallbacks[data.event]
    if (callback) {
      callback(data.payload)
    }
  })
  
  return socket
}

export const onSocketEvent = (event, callback) => {
  eventCallbacks[event] = callback
}

export const emitSocketEvent = (event, payload) => {
  if (socket) {
    socket.emit('message', { event, payload })
  }
}

2. 聊天页面实现

<template>
  <view class="chat-container">
    <scroll-view 
      scroll-y 
      :scroll-top="scrollTop"
      scroll-with-animation
      class="message-list">
      
      <view 
        v-for="(msg, index) in messages" 
        :key="index"
        :class="['message-item', msg.sender === userInfo.id ? 'right' : 'left']">
        <image 
          :src="msg.avatar" 
          class="avatar" />
        <view class="content">
          <text v-if="msg.type === 'text'">{{ msg.content }}</text>
          <image 
            v-else-if="msg.type === 'image'"
            :src="msg.content"
            mode="widthFix"
            @click="previewImage(msg.content)" />
        </view>
      </view>
    </scroll-view>
    
    <view class="input-area">
      <u-input 
        v-model="inputMsg"
        placeholder="输入消息..."
        @confirm="sendText" />
      <u-button 
        type="primary" 
        @click="sendText">
        发送
      </u-button>
    </view>
  </view>
</template>

<script>
import { onSocketEvent, emitSocketEvent } from '@/utils/libs/socket'

export default {
  data() {
    return {
      messages: [],
      inputMsg: '',
      scrollTop: 0,
      userInfo: {}
    }
  },
  
  onLoad(options) {
    this.userInfo = this.$store.state.user.info
    this.targetId = options.targetId
    
    // 初始化WebSocket
    this.initSocket()
    
    // 加载历史消息
    this.loadHistory()
  },
  
  methods: {
    initSocket() {
      onSocketEvent('chat_message', this.handleNewMessage)
    },
    
    handleNewMessage(msg) {
      if (msg.sender === this.targetId || msg.sender === this.userInfo.id) {
        this.messages.push(msg)
        this.scrollToBottom()
      }
    },
    
    async loadHistory() {
      const res = await this.$http.get('/chat/history', {
        params: { targetId: this.targetId }
      })
      this.messages = res.data.list
      this.$nextTick(() => {
        this.scrollToBottom()
      })
    },
    
    scrollToBottom() {
      setTimeout(() => {
        this.scrollTop = 99999
      }, 100)
    },
    
    sendText() {
      if (!this.inputMsg.trim()) return
      
      const msg = {
        type: 'text',
        content: this.inputMsg,
        sender: this.userInfo.id,
        receiver: this.targetId,
        avatar: this.userInfo.avatar,
        time: Date.now()
      }
      
      emitSocketEvent('chat_message', msg)
      this.messages.push(msg)
      this.inputMsg = ''
      this.scrollToBottom()
    },
    
    previewImage(url) {
      uni.previewImage({
        urls: [url]
      })
    }
  }
}
</script>

五、多平台适配技巧

1. 条件编译处理平台差异

// 获取系统状态栏高度
function getStatusBarHeight() {
  // #ifdef MP-WEIXIN
  const info = uni.getSystemInfoSync()
  return info.statusBarHeight
  // #endif
  
  // #ifdef APP-PLUS
  return plus.navigator.getStatusbarHeight()
  // #endif
  
  // #ifdef H5
  return 0
  // #endif
}

// 平台特定样式处理
<view class="container" :style="{ paddingTop: statusBarHeight + 'px' }">
  <!-- 内容区域 -->
</view>

2. 原生能力封装

// utils/native.js
export const chooseImage = (options = {}) => {
  return new Promise((resolve, reject) => {
    // #ifdef APP-PLUS
    plus.gallery.pick(
      res => resolve(res.files),
      err => reject(err),
      options
    )
    // #endif
    
    // #ifdef MP-WEIXIN || H5
    uni.chooseImage({
      count: options.count || 9,
      sizeType: options.sizeType || ['original', 'compressed'],
      sourceType: options.sourceType || ['album', 'camera'],
      success: res => resolve(res.tempFilePaths),
      fail: err => reject(err)
    })
    // #endif
  })
}

export const getLocation = () => {
  return new Promise((resolve, reject) => {
    // #ifdef APP-PLUS
    plus.geolocation.getCurrentPosition(
      pos => resolve({
        latitude: pos.coords.latitude,
        longitude: pos.coords.longitude
      }),
      err => reject(err)
    )
    // #endif
    
    // #ifdef MP-WEIXIN
    uni.getLocation({
      type: 'gcj02',
      success: res => resolve(res),
      fail: err => reject(err)
    })
    // #endif
    
    // #ifdef H5
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        pos => resolve({
          latitude: pos.coords.latitude,
          longitude: pos.coords.longitude
        }),
        err => reject(err)
      )
    } else {
      reject(new Error('浏览器不支持地理位置'))
    }
    // #endif
  })
}

六、性能优化方案

1. 图片懒加载与压缩

<!-- 使用uView的懒加载组件 -->
<u-lazy-load 
  :image="item.image" 
  :loading-img="placeholderImg"
  :error-img="errorImg">
</u-lazy-load>

// 图片压缩处理
async compressImage(filePath) {
  // #ifdef APP-PLUS
  const result = await new Promise((resolve) => {
    plus.zip.compressImage({
      src: filePath,
      dst: `_doc/compressed/${Date.now()}.jpg`,
      quality: 70,
      width: '50%',
      height: '50%'
    }, resolve)
  })
  return result.target
  // #endif
  
  // #ifdef MP-WEIXIN
  const res = await uni.compressImage({
    src: filePath,
    quality: 70
  })
  return res.tempFilePath
  // #endif
  
  // #ifdef H5
  return filePath // H5端暂不处理压缩
  // #endif
}

2. 分包加载配置

// manifest.json 配置
{
  "mp-weixin": {
    "optimization": {
      "subPackages": true
    }
  }
}

// pages.json 分包配置
{
  "pages": [...],
  "subPackages": [
    {
      "root": "pages/social",
      "pages": [
        { "path": "moment/index" },
        { "path": "chat/index" }
      ]
    },
    {
      "root": "pages/user",
      "pages": [
        { "path": "profile/index" },
        { "path": "settings/index" }
      ]
    }
  ],
  "preloadRule": {
    "pages/home/index": {
      "network": "all",
      "packages": ["pages/social"]
    }
  }
}

七、打包发布流程

1. 微信小程序发布

# 生产环境打包
npm run build:mp-weixin

# 使用微信开发者工具
1. 导入项目:选择/unpackage/dist/build/mp-weixin目录
2. 上传代码:点击"上传"按钮
3. 登录微信公众平台提交审核

2. App打包发布

# Android打包
npm run build:app-plus

# iOS打包
1. 使用HBuilderX打开项目
2. 选择"发行" → "原生App-云打包"
3. 配置证书和描述文件
4. 提交打包并下载ipa文件
5. 上传到App Store Connect

八、总结与扩展

本教程完整实现了基于Uniapp的社交应用开发:

  1. 搭建了跨平台开发环境
  2. 实现了核心社交功能
  3. 集成了即时通讯能力
  4. 优化了多端适配方案
  5. 完成了应用发布流程

扩展方向:

  • 音视频通话功能集成
  • 社交推荐算法实现
  • AR虚拟形象功能
  • 国际化多语言支持

完整项目代码已上传GitHub:https://github.com/example/uniapp-social-app

Uniapp社交类APP开发全流程 | 跨平台应用实战指南
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 uniapp Uniapp社交类APP开发全流程 | 跨平台应用实战指南 https://www.taomawang.com/web/uniapp/814.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务