闪递校园:基于uni-app的校园综合服务平台开发实战

作为一名全栈开发者,我用6个月时间开发了这款校园综合服务平台——闪递校园。本文将详细分享项目从0到1的开发经验,包括技术选型、核心功能实现、踩坑记录以及性能优化等方面的干货内容。

📝 项目背景

在这里插入图片描述

为什么要做这个项目?

在大学期间,我发现校园里存在诸多痛点:

  • 🏃‍♂️ 跑腿需求旺盛:取快递、买外卖、代购等需求量大,但缺乏规范化平台
  • 💬 社交圈子固化:同学之间交流局限,缺乏破冰工具
  • 🎫 信息不对称:演出票务、二手交易信息分散
  • 💰 支付体验差:校园服务多采用现金支付,体验落后

于是我决定开发一款集跑腿服务、社交娱乐、校园电商于一体的综合性平台。

技术选型思考

技术栈选择理由
前端框架uni-app + Vue 2.x一套代码多端运行,降低开发成本
UI组件库TuniaoUI组件丰富,校园风格契合
状态管理Vuex用户信息、订单状态等需要全局管理
地图服务腾讯地图API国内定位精准,校园场景适配好
支付系统微信支付 + 余额支付覆盖主流支付场景
实时通讯WebSocket订单状态、聊天消息实时性要求高

🏗️ 项目架构设计

整体架构图

核心模块
跑腿服务
社交圈子
支付系统
实时通讯
用户层
UI组件层
业务逻辑层
数据管理层
网络请求层
后端API

目录结构设计

reWxSchool/
├── pages/              # 主包页面
│   ├── index/         # 首页模块
│   ├── hall/          # 接单大厅
│   ├── circle/        # 校园圈子
│   └── user/          # 用户中心
├── pagesA/            # 分包A - 核心业务
│   ├── order/         # 订单管理
│   ├── chat/          # 聊天系统
│   ├── makeFrend/     # 盲盒交友
│   └── withdraw/      # 钱包系统
├── pagesB/            # 分包B - 扩展功能
│   ├── movie/         # 电影票务
│   ├── secondhand/    # 二手市场
│   └── mbti/          # 性格测试
├── components/        # 公共组件
├── store/            # Vuex状态管理
└── util/             # 工具函数

💡 核心功能实现

1. 复杂支付系统的设计与实现

支付系统是整个平台的核心,我设计了一套完整的支付流程:

1.1 支付组件封装
// components/payBox/payBox.vue
export default {data() {return {paymentInProgress: false,      // 支付进行中标志isProcessing: false,           // 处理中标志currentOrderId: null,          // 当前订单IDpaymentSessionId: null,        // 支付会话IDretryCount: 0,                 // 重试次数paymentTimeout: null           // 支付超时定时器}},methods: {// 核心支付流程async processPayment() {if (this.paymentInProgress) {this.showToast('支付进行中,请稍候');return;}try {this.paymentInProgress = true;this.isProcessing = true;this.paymentStartTime = Date.now();this.retryCount++;// 设置支付超时(5分钟)this.setPaymentTimeout();// 保存订单(如果还没有订单ID)let orderResult;if (!this.currentOrderId) {orderResult = await this.saveOrder();this.currentOrderId = orderResult.id || orderResult.payOrderId;} else {// 使用现有订单ID,避免重复创建订单orderResult = { payOrderId: this.currentOrderId, id: this.currentOrderId };}// 发起支付await this.initiatePayment(orderResult.payOrderId, orderResult.id);} catch (error) {this.handlePaymentError(error);} finally {this.clearPaymentTimeout();// 延迟重置状态,防止快速重复点击setTimeout(() => {this.isProcessing = false;this.paymentInProgress = false;}, 1000);}},// 微信支付处理async wechatPayment(payOrderId, orderId) {try {const response = await this.$apiHttp.transactions({ payOrderId });if (response.code !== 0) {throw new Error('交易初始化失败');}await this.requestWechatPayment(response.data, orderId);} catch (error) {console.log('支付失败', orderId);}},// 余额支付处理async balancePayment(payOrderId, orderId) {try {const response = await this.$apiHttp.balancePayment({ payOrderId });if (response.code === 0) {this.handlePaymentSuccess(orderId);} else {this.handlePaymentFailure(response.msg, orderId);}} catch (error) {this.handlePaymentFailure('余额支付失败', orderId);}}}
}
1.2 防重复支付机制

为了防止用户快速点击导致重复扣费,我设计了多重保护机制:

// 多重锁定机制
confirmBalancePayment() {// 防止快速点击确认框if (this.isProcessing || this.paymentInProgress) {return;}uni.showModal({title: '确认支付',content: '是否使用余额免密支付?',success: (res) => {if (res.confirm) {// 再次检查状态,防止确认框期间状态变化if (!this.isProcessing && !this.paymentInProgress) {this.processPayment();}}},});
}

2. 实时通讯系统实现

2.1 WebSocket连接管理
// pagesA/chatUser/chat.vue
export default {data() {return {socket: null,wsUrl: "wss://school.bitsai.top/school-api/websocket",connectionStatus: 'disconnected',token: "",reconnectAttempts: 0,maxReconnectAttempts: 5}},methods: {// WebSocket连接connectWebSocket() {// 检查token是否存在if (!this.token) {console.error('Token不存在,无法建立WebSocket连接');this.showErrorMessage('登录信息无效,请重新登录');return;}this.socket = uni.connectSocket({url: this.wsUrl,header: {'token': this.token},success: () => console.log('WebSocket connection established'),fail: (error) => {console.error('WebSocket connection failed', error);this.showErrorMessage('WebSocket连接失败,请稍后重试');}});// 处理连接打开this.socket.onOpen(() => {console.log('WebSocket connection opened successfully');this.connectionStatus = 'connected';this.reconnectAttempts = 0; // 重置重连次数});// 处理接收消息this.socket.onMessage((message) => {let data = JSON.parse(message.data);if (data.type === 0) {this.handleReceivedMessage(data.msg);}});// 处理连接关闭,自动重连(指数退避)this.socket.onClose((event) => {if (event.code !== 1000) {this.connectionStatus = 'reconnecting';// 确保token存在时才重连if (this.token && this.reconnectAttempts < this.maxReconnectAttempts) {setTimeout(() => {this.reconnectAttempts++;this.connectWebSocket();}, this.getReconnectDelay());} else {this.connectionStatus = 'disconnected';}} else {this.connectionStatus = 'disconnected';}});},// 指数退避重连延迟getReconnectDelay() {return Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);},// 消息发送handleSend() {if (this.chatMsg && !/^\s+$/.test(this.chatMsg)) {let messageObj = {botContent: "",userContent: this.chatMsg,isload: true,iserr: false,sendTime: new Date().toISOString()};this.msgList.push(messageObj);this.shouldScrollToBottom = true;if (this.socket && this.socket.readyState === 1) {const message = {type: 0,msg: this.chatMsg,data: "Some data",userId: this.receiverId};this.socket.send({data: JSON.stringify(message),success: () => {console.log('Message sent successfully');this.updateMessageStatus('success');},fail: (error) => {console.error('Failed to send message', error);this.updateMessageStatus('error');}});} else {console.error('WebSocket is not open');this.updateMessageStatus('error');this.showErrorMessage('无法发送消息,请稍后重试');}this.chatMsg = ''; // 清空输入框}}}
}

3. 地理位置服务集成

3.1 腾讯地图API封装
// pagesB/utils/power.js
export default {methods: {// 获取用户定位信息Monitor() {var QQMapWX = require('@/comne/qqmap-wx-jssdk.js');let qqmapsdk = new QQMapWX({key: ''});var _this = this;uni.getLocation({type: 'gcj02',success(res) {var latitude = res.latitude;var longitude = res.longitude;// 小程序环境使用腾讯地图SDK// #ifdef MP-WEIXINqqmapsdk.reverseGeocoder({location: {latitude: latitude,longitude: longitude},success: function(res) {console.log(res.result);uni.setStorageSync('LocalHost', res.result);_this.city = res.result.ad_info.city;}});// #endif// H5环境使用HTTP API// #ifdef H5_this.getH5Loca(latitude, longitude);// #endif},fail(err) {console.log(err);uni.showModal({title: '提示',content: '请先授权位置信息',success: function(res) {if (res.cancel == false && res.confirm) {uni.openSetting({success: function(data) {_this.Monitor();}});} else {_this.Monitor();}}});}});},// H5环境地理编码getH5Loca(latitude, longitude) {let prm = {location: {latitude: latitude,longitude: longitude},key: '',output: 'jsonp',get_poi: 0,coord_type: 5};let url = `https://apis.map.qq.com/ws/geocoder/v1/?location=${latitude},${longitude}`;this.$jsonp(url, prm).then(res => {uni.setStorageSync('LocalHost', res.result);this.city = res.result.ad_info.city;});}}
}

4. AI智能客服集成

// util/request/api.js
coQuanziApi: (data) => {return new Promise((resolve, reject) => {uni.request({url: 'https://api.coze.cn/open_api/v2/chat',method: 'POST',header: {'Authorization': '','Connection': 'keep-alive','Host': 'api.coze.cn','Accept': '*/*','Content-Type': 'application/json',},data: {"conversation_id": data.userId,"bot_id": "","user": data.userId,"stream": false,"query": data.content,},success: (res) => {resolve(res);},fail: (err) => {console.error(err);reject(err);}});});
}

🎯 创新功能设计

1. 盲盒交友系统

这是我最引以为豪的创新功能,通过匿名投递和随机抽取的方式,为校园社交带来新的玩法:

核心逻辑:
  1. 投放纸条:用户可以匿名投放个人信息或心情到盲盒中
  2. 随机抽取:其他用户可以随机抽取纸条,如果感兴趣可以发起聊天
  3. 地理限制:仅限同校学生参与,确保社交的真实性
  4. 隐私保护:初期完全匿名,建立信任后才可以选择公开身份
// 盲盒相关API
// 投放纸条
luckyNoteSave: (data) => {return postRequest('luckyNote/save', data)
},// 抽取纸条
luckydrawNote: (data) => {return postRequest('luckyNote/drawNote', data)
},// 查看历史
noteHistoryPage: (data) => {return getRequest('luckyNote/noteHistoryPage', data)
}

2. 智能跑腿订单系统

订单状态流转:
骑手接单
骑手取货
骑手送达
用户确认
用户取消
待接单
待取货
配送中
待确认
已完成
已取消
核心API设计:
// 订单相关API
orderSave: (data) => postRequest('order/save', data),          // 创建订单
orderTaking: (data) => postRequest('order/taking', data),      // 骑手接单
orderDelivery: (data) => postRequest('order/delivery', data),  // 骑手取货
orderArrive: (data) => postRequest('order/arrive', data),      // 骑手送达
orderConfirmArrive: (data) => postRequest('order/confirmArrive', data), // 用户确认

🚀 性能优化实践

1. 分包加载策略

通过合理的分包设计,将首屏加载时间从3.2s优化到1.8s:

// pages.json
{"pages": [// 主包 - 核心页面{"path": "pages/index/index"},{"path": "pages/hall/index"},{"path": "pages/circle/circle"},{"path": "pages/user/index"}],"subPackages": [{"root": "pagesA",  // 分包A - 核心业务功能"pages": [{"path": "order/index"},{"path": "chat/chat"},{"path": "makeFrend/makeFrend"}]},{"root": "pagesB",  // 分包B - 扩展功能"pages": [{"path": "movie/index"},{"path": "secondhand/index"},{"path": "mbti/mbti"}]}]
}

2. 请求优化

API管理器设计:
// util/request/api.js
const apiManager = {// 用户相关login: (data) => postRequest('api/login', data),getUserData: () => getRequest('user/data'),// 订单相关orderList: (data) => getRequest('order/pageList', data),orderSave: (data) => postRequest('order/save', data),// 支付相关transactions: (data) => getRequest('pay/transactions', data),balancePayment: (data) => getRequest('pay/balancePayment', data),// 聊天相关getChatList: (data) => getRequest('chat/getChatList', data),chatPageList: (data) => getRequest('chat/message/pageList', data)
}

3. 状态管理优化

// store/index.js
const store = new Vuex.Store({state: {// 持久化用户信息vuex_user: lifeData.vuex_user ? lifeData.vuex_user : {name: '图鸟'},// 导航栏配置vuex_custom_nav_bar: true,vuex_status_bar_height: 0,vuex_custom_bar_height: 0,},mutations: {// 通用状态更新$tStore(state, payload) {let nameArr = payload.name.split('.');let saveKey = '';let len = nameArr.length;if (len >= 2) {// 支持多层级状态更新let obj = state[nameArr[0]];for (let i = 1; i < len - 1; i++) {obj = obj[nameArr[i]];}obj[nameArr[len - 1]] = payload.value;saveKey = nameArr[0];} else {state[payload.name] = payload.value;saveKey = payload.name;}// 自动持久化到本地存储saveLifeData(saveKey, state[saveKey]);}}
})

🐛 踩坑记录与解决方案

1. 微信支付回调问题

问题: 微信支付成功后,有时候回调不及时,导致订单状态更新延迟。

解决方案:

// 增加支付状态轮询机制
requestWechatPayment(paymentData, orderId) {return new Promise((resolve, reject) => {uni.requestPayment({...paymentData,success: () => {// 支付成功后启动状态轮询this.startPaymentPolling(orderId);this.handlePaymentSuccess(orderId);},fail: (error) => {this.handlePaymentFailure('微信支付失败', orderId);}});});
},// 支付状态轮询
startPaymentPolling(orderId) {const pollInterval = setInterval(async () => {try {const result = await this.$apiHttp.orderDetail(orderId);if (result.data.payStatus === 'PAID') {clearInterval(pollInterval);this.updateOrderStatus('paid');}} catch (error) {console.error('轮询支付状态失败', error);}}, 2000);// 30秒后停止轮询setTimeout(() => clearInterval(pollInterval), 30000);
}

2. WebSocket断线重连问题

问题: 用户切换应用或网络波动时,WebSocket连接容易断开,重连策略不够完善。

解决方案:

// 改进的重连机制
connectWebSocket() {// ... 连接逻辑this.socket.onClose((event) => {if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) {this.connectionStatus = 'reconnecting';// 指数退避策略const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);setTimeout(() => {this.reconnectAttempts++;console.log(`${this.reconnectAttempts}次重连尝试`);this.connectWebSocket();}, delay);} else {this.connectionStatus = 'disconnected';if (this.reconnectAttempts >= this.maxReconnectAttempts) {this.showErrorMessage('连接失败次数过多,请检查网络后手动重试');}}});
}

3. 地理位置权限问题

问题: 用户首次使用时拒绝地理位置权限,导致功能异常。

解决方案:

Monitor() {uni.getLocation({type: 'gcj02',success(res) {// 定位成功处理},fail(err) {console.log(err);// 优雅的权限引导uni.showModal({title: '定位权限',content: '为了给您提供更好的校园服务,需要获取您的位置信息',confirmText: '去设置',cancelText: '手动选择',success: function(res) {if (res.confirm) {// 引导用户去设置页面uni.openSetting({success: function(data) {if (data.authSetting['scope.userLocation']) {_this.Monitor(); // 重新获取定位}}});} else {// 提供手动选择城市的备选方案uni.navigateTo({url: '/pagesA/school/school'});}}});}});
}

🎉 结语

闪递校园这个项目让我对全栈开发有了更深入的理解,也让我意识到技术服务于业务、业务服务于用户的重要性。虽然项目还有很多可以优化的地方,但它已经成为我技术成长路上的一个重要里程碑。

希望这篇文章能够对正在学习 uni-app 开发或者想要做校园服务类项目的同学有所帮助。如果你有任何问题或建议,欢迎在评论区交流讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/95047.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95047.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/95047.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt::Q_INIT_RESOURCE用法

q_init_resource 用法 q_init_resource 是 Qt 框架中用于初始化嵌入式资源的一个函数。它通常用于将编译到应用程序二进制文件中的资源&#xff08;如图像、QML文件、翻译文件等&#xff09;注册到Qt的资源系统中。 基本用法 cpp Q_INIT_RESOURCE(resourcename); 其中 resource…

【开题答辩全过程】以 基于php的校园兼职求职网站为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

安卓悬浮球-3566-测试报告

安卓悬浮球-3566-测试报告 测试概述 项目名称: 悬浮球电子秤应用 测试版本: v1.0.0 测试时间: 2025年9月 测试环境: UniApp开发环境 测试类型: 功能测试、性能测试、兼容性测试 测试结果: 见附件测试环境配置 硬件环境 测试设备: Android 内置3566屏幕分辨率: 1080x1920内存: 2…

《C++进阶之STL》【红黑树】

【红黑树】目录前言&#xff1a;------------概念介绍------------1. 什么是红黑树&#xff1f;2. 红黑树的基本特性是什么&#xff1f;3. 红黑树的效率怎么样&#xff1f;4. 红黑树如何确保最长路径不超过最短路径的2倍&#xff1f;------------基本操作------------一、查找操…

Java全栈工程师的实战面试:从基础到微服务

Java全栈工程师的实战面试&#xff1a;从基础到微服务 在一次真实的面试中&#xff0c;一位经验丰富的Java全栈开发工程师被问及多个技术问题。他的名字是林浩然&#xff0c;28岁&#xff0c;拥有计算机科学与技术硕士学位&#xff0c;拥有5年的工作经验。他曾在一家大型互联网…

工业物联网(IIoT)+ AI:智能工业的未来趋势全解析

工业物联网&#xff08;IIoT&#xff09; AI&#xff1a;智能工业的未来趋势全解析 文章目录工业物联网&#xff08;IIoT&#xff09; AI&#xff1a;智能工业的未来趋势全解析摘要什么是工业物联网&#xff08;IIoT&#xff09;&#xff1f;1. IIoT 的定义2. IIoT 与传统 IoT …

3000. 对角线最长的矩形的面积

3000. 对角线最长的矩形的面积 题目链接&#xff1a;3000. 对角线最长的矩形的面积 代码如下&#xff1a; class Solution { public:int areaOfMaxDiagonal(vector<vector<int>>& dimensions) {double maxDiagonalLength 0;int res 0;for (vector<int&g…

Scikit-learn Python机器学习 - 什么是机器学习

锋哥原创的Scikit-learn Python机器学习视频教程&#xff1a; 2026版 Scikit-learn Python机器学习 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 课程介绍 本课程主要讲解基于Scikit-learn的Python机器学习知识&#xff0c;包括机器学习概述&#xff0c;特征工程(数据…

Python环境搭建报错

检查Python版本兼容性确保下载的Python版本与操作系统匹配&#xff08;如Windows 32位/64位、macOS ARM/x86&#xff09;。可通过命令行输入python --version或python3 --version验证已安装版本是否与需求一致。清理残留文件若之前安装失败&#xff0c;需手动删除残留文件。Win…

C# WinForm中提供webapi服务

云川给我提了一个需求&#xff0c;要我开发一个API服务程序&#xff0c;他来调用&#xff0c;程序再去明道云取数&#xff0c;计算出一个结果返回。网上找到了一篇文章&#xff1a;C# 在Windform程序中搭建Webapi - 小码哥-风云 - 博客园&#xff0c;可以使用微软提供的Microso…

Linux中使用docker部署solr

1. 运行一次&#xff0c;然后拉取镜像 [rootinstance-yo4hab98 ~]# docker run -d -p 8983:8983 --name solr-8.11.3 -t solr:8.11.3 ps 镜像相关指令 # 查看镜像 docker images# 删除镜像 指定名称和版本删除 docker rmi nginx:latest # 删除镜像 指定id删除 docker rm…

代谢组学分析指南

摘要代谢组学是个新兴领域&#xff0c;系统性地定量众多代谢物。关键目的是识别与每种生物表型相对应的代谢物&#xff0c;并进一步分析其中涉及的机制。尽管代谢组学对于理解相关的生物学现象至关重要&#xff0c;但在全面描述过程的能力上存在局限性。推荐采用综合分析策略&a…

vue2使用el-form动态参数展示并非空校验

需求&#xff1a;需要根据类型type动态显示某些参数&#xff0c;并且后端需要的参数也不同&#xff0c;比如type为1&#xff1a;后端要aa和bb参数&#xff0c;type为2&#xff1a;后端要cc和dd参数&#xff0c;前端显示的字段名也不一样&#xff0c;但是样式是不变的。1.效果2.…

(附源码)基于Vue的教师档案管理系统的设计与实现

摘 要 随着信息技术的不断发展&#xff0c;学校管理工作正逐渐从纸质化向数字化转型。教师档案管理作为学校管理的重要环节&#xff0c;其信息化和高效化对于提升学校管理水平具有重要意义。本文设计并实现了一个基于Vue框架的教师档案管理系统&#xff0c;旨在通过前端技术的…

运算电源抑制比(PSRR)测量及设计注意事项

1、简介如果运放的供电电源发生变化&#xff0c;输出不应发生变化&#xff0c;但实际运放随着供电电源的波动&#xff0c;运放输出也将会发生波动。折合到输出端&#xff0c;PSRR定义 Xv(电源电压波动) / Yv&#xff08;输出电压波动&#xff09;&#xff0c;该量为无量纲&…

YOLOv8-SMOT:一种高效鲁棒的实时小目标跟踪框架:基于切片辅助训练与自适应关联

https://arxiv.org/pdf/2507.12087 摘要 从无人机&#xff08;UAV&#xff09;视角对小型敏捷多目标&#xff08;SMOT&#xff09;——例如鸟类——进行跟踪是一项极具挑战性的计算机视觉任务。该任务的难点主要源于三个方面&#xff1a;目标外观特征极度稀缺、相机与目标自身复…

深入理解QLabel:Qt中的文本与图像显示控件

在Qt框架中&#xff0c;QLabel是一个功能强大且灵活的控件&#xff0c;主要用于在用户界面上显示文本或图像。无论是在简单的信息提示&#xff0c;还是在复杂的图形界面中&#xff0c;QLabel都能发挥重要作用。本文将详细介绍QLabel的主要功能、使用方法以及一些高级技巧&#…

hintcon2025 Verilog OJ

#web题目要求我们执行 /readflag give me the flagif ((strcmp(argv[1], "give") | strcmp(argv[2], "me") | strcmp(argv[3], "the") | strcmp(argv[4], "flag")) ! 0) {puts("You are not worthy");return 1; }首先&#…

佳易王钟表维修养护管理系统:开启钟表维修高效管理新篇章​就#软件操作教程

前言&#xff1a; &#xff08;一&#xff09;试用版获取方式 资源下载路径&#xff1a;进入博主头像主页第一篇文章末尾&#xff0c;点击卡片按钮&#xff1b;或访问左上角博客主页&#xff0c;通过右侧按钮获取详细资料。 说明&#xff1a;下载文件为压缩包&#xff0c;使用…

mysql组提交

Binlog Group Commit1. 背景&#xff1a;没有组提交时的问题&#xff08;MySQL 5.7及更早版本的痛点&#xff09;在早期版本的MySQL中&#xff0c;为了保证二进制日志&#xff08;Binlog&#xff09; 和 存储引擎&#xff08;如InnoDB&#xff09; 之间的一致性&#xff08;即一…