LikeShop 小程序端二开:前端工程结构、接口封装与登录态处理
2026/9/24 21:33:21 网站建设 项目流程

一、前言

在之前的系列文章中,我从服务端视角写了 LikeShop 的分层架构、支付模块和数据库设计。但后台收到不少读者留言,说“服务端搞明白了,但小程序端的前端代码还是不知道怎么改”。这让我意识到,移动端作为用户直接接触的入口,它的工程结构和数据流转同样值得单独讲一篇

LikeShop 的移动端基于 uni-app 构建,一套代码可以编译出微信小程序、H5、安卓 App、iOS App 等多个终端。这种“一套代码多端发布”的能力,既是它的优势,也是二开时的难点——你需要理解哪些代码是多端共用的,哪些需要做条件编译。

这篇文章就从 uniapp 工程的目录结构出发,把接口封装机制和登录态处理链路拆开讲清楚。内容基于 LikeShop 单商户版 v3.5.1 的 uniapp 源码。

二、uniapp 工程整体结构

根目录布局

移动端源码位于根目录的uniapp/文件夹下,与server/(服务端)、admin/(管理后台前端)、pc/(PC 前台)并列。在完整源码中,只有server/需要部署到服务器,其他几个前端工程是前后端分离的独立项目,本地开发时通过 npm 运行,编译产物再部署到对应的静态资源目录。

uniapp/内部的结构大致如下:

uniapp/ ├── config/ │ └── app.js # 服务端地址等全局配置 ├── api/ # 接口定义层 ├── components/ # 全局公共组件 ├── pages/ # 页面文件 ├── static/ # 静态资源(图片、字体等) ├── store/ # 状态管理 ├── utils/ # 工具函数 ├── .env.development # 开发环境变量 ├── .env.production # 生产环境变量 ├── main.js # 应用入口 ├── App.vue # 应用根组件 ├── pages.json # 页面路由与导航栏配置 ├── manifest.json # 多端打包配置 └── package.json # 依赖与脚本

这里有两个文件需要特别关注:pages.json定义了所有页面的路由、导航栏样式和 tabBar 配置;manifest.json则存储了小程序 AppID、H5 基础路径、APP 图标等各端打包参数。二开时如果新增页面,必须先在pages.json中注册路由,否则页面无法被访问。

多端条件编译

uni-app 的条件编译是二开时最常用的特性之一。在 LikeShop 的代码中,你会看到大量类似这样的写法:

// #ifdef MP-WEIXIN// 微信小程序专属逻辑// #endif// #ifdef H5// H5 专属逻辑// #endif

微信小程序的登录流程和 H5 完全不同——小程序通过wx.login()获取 code 再换取 openid,而 H5 在微信环境下走公众号授权。条件编译让两套逻辑可以共存于同一个文件,编译到不同平台时自动裁剪。二开时添加平台专属逻辑,也应该用条件编译包裹,不要直接写 if/else 判断平台,那样会导致代码在编译后被完整打包进所有端。

环境变量配置

uniapp/目录下有两个环境变量文件:.env.development.env.production。官方仓库中提供的是.env.development.example.env.production.example,需要复制并去掉.example后缀才能生效。

关键配置项是服务端接口地址:

VITE_APP_BASE_URL='http://你的服务端域名'

官方文档特别强调不要使用 localhost,因为 uni-app 在多端运行时对本地地址的解析不一致。本地开发时也要用一个可访问的域名(可以通过 hosts 映射到 127.0.0.1)。

三、接口封装机制

为什么需要封装

LikeShop 的移动端有几十个接口调用场景——商品列表、订单详情、用户信息、支付请求等。如果每个页面都直接用uni.request(),会带来三个问题:请求头不统一(token 漏传)、错误处理重复、后端接口变更时改动分散

LikeShop 的解决方案是在utils/目录下封装一个统一的请求模块,所有接口调用都经过它。

请求封装的核心结构

LikeShop 的请求封装(不同版本文件名可能不同,通常在utils/request.jscommon/request.js中)大致遵循以下结构:

// utils/request.js(示意结构)constrequest=(options)=>{// 1. 拼接基础 URLconsturl=config.baseUrl+options.url// 2. 构建请求头,自动注入 tokenconstheader={'Content-Type':'application/json','version':APP_VERSION,...options.header}consttoken=uni.getStorageSync('token')if(token){header['token']=token}// 3. 发起请求returnnewPromise((resolve,reject)=>{uni.request({url,method:options.method||'GET',data:options.data,header,success:(res)=>{// 4. 统一处理响应if(res.data.code===1){resolve(res.data)}elseif(res.data.code===-1){// token 失效,跳转登录uni.navigateTo({url:'/pages/login/login'})reject(res.data)}else{uni.showToast({title:res.data.msg,icon:'none'})reject(res.data)}},fail:reject})})}exportdefaultrequest

这段代码体现了几个关键设计:

Token 自动注入。每次请求前从本地存储读取 token 并放入请求头,业务页面不需要关心 token 的存在。

统一响应处理。LikeShop 的接口返回格式为{ code, show, msg, data },其中code为 1 表示成功,0 表示失败,-1 表示需要重新登录。请求封装层统一处理这三种情况,-1时自动跳转登录页,业务页面只需要处理成功逻辑。

Promise 风格。封装返回 Promise,页面中可以用await request({...})的方式调用,代码更简洁。

接口定义层的组织方式

在请求封装之上,LikeShop 通常还有一个API 定义层api/目录),把接口按模块分类组织:

api/ ├── goods.js # 商品相关接口 ├── order.js # 订单相关接口 ├── user.js # 用户相关接口 ├── cart.js # 购物车接口 └── pay.js # 支付接口

以订单模块为例,api/order.js中会定义:

importrequestfrom'@/utils/request'exportfunctiongetOrderList(params){returnrequest({url:'/shopapi/order/orderList',data:params})}exportfunctiongetOrderDetail(id){returnrequest({url:'/shopapi/order/orderDetail',data:{id}})}exportfunctioncreateOrder(data){returnrequest({url:'/shopapi/order/create',method:'POST',data})}

页面中调用时只需要import { getOrderList } from '@/api/order',不关心 URL 拼接和请求头处理。二开时如果后端接口路径变了,只需要改api/目录下的定义,不需要全项目搜索替换 URL。

四、登录态处理

登录流程

LikeShop 支持多种登录方式:手机号码密码登录、手机短信验证码登录、微信授权登录等。其中微信小程序授权登录是移动端最常用的方式,也是二开时最容易踩坑的环节。

小程序端的登录流程如下:

用户点击登录 → wx.login() 获取 code ↓ 将 code 发送到服务端 /shopapi/user/mnpLogin ↓ 服务端调用微信接口换取 openid 和 session_key ↓ 服务端查找或创建用户,生成 token 返回 ↓ 前端将 token 存储到本地 ↓ 后续请求自动携带 token

这里的关键是:code 只能使用一次,且有效期很短。前端拿到 code 后应立刻发送给服务端,不要在本地缓存。二开时如果遇到“登录返回 401”或“code 无效”的报错,通常是因为 code 被重复使用或超时了。

Token 的存储与管理

LikeShop 使用Pinia作为状态管理库(Vue 2 版本使用 Vuex),在 store 中管理用户信息和 token。

典型的 Pinia store 结构如下:

// store/user.js(示意结构)import{defineStore}from'pinia'import{ref}from'vue'exportconstuseUserStore=defineStore('user',()=>{consttoken=ref(uni.getStorageSync('token')||'')constuserInfo=ref(uni.getStorageSync('userInfo')||{})functionsetToken(val){token.value=val uni.setStorageSync('token',val)// 持久化}functionsetUserInfo(info){userInfo.value=info uni.setStorageSync('userInfo',info)}functionlogout(){token.value=''userInfo.value={}uni.removeStorageSync('token')uni.removeStorageSync('userInfo')}return{token,userInfo,setToken,setUserInfo,logout}})

Token 的持久化存储在uni.setStorageSync。小程序端的 Storage 是持久化的,除非用户主动删除小程序或系统清理缓存。H5 端则存储在 localStorage 中。不要只把 token 存在 Pinia 的 ref 里,页面刷新(H5 场景)会导致内存状态丢失,用户会被迫重新登录。

Token 失效的自动处理

Token 失效是二开时最常见的登录态问题。LikeShop 的接口在 token 过期时会返回code: -1,请求封装层捕获到-1后,会执行以下操作:

  1. 清除本地存储的 token 和用户信息
  2. 跳转到登录页
  3. 登录成功后返回原页面

这里有一个容易被忽略的细节:并发请求时的重复跳转。如果页面同时发起了 3 个请求,token 都失效了,3 个请求都会收到-1,如果不加控制,登录页会被跳转 3 次。解决方案是加一个全局状态锁

letisRedirecting=falseif(res.data.code===-1&&!isRedirecting){isRedirecting=trueuni.removeStorageSync('token')uni.navigateTo({url:'/pages/login/login',complete:()=>{isRedirecting=false}})}

多端登录态的差异

LikeShop 的一个核心设计是“全终端数据打通”——用户在 H5 下的订单,小程序端的订单列表里能同步看到。这意味着登录态需要在服务端统一管理,而不是绑定在某个终端上

服务端的UserTokenService负责管理 token 的生成和更新。当用户在某个终端登录时,服务端会在ls_user_session表中记录该终端的 token;同一用户在不同终端登录时,会生成不同的 token,但都指向同一个 user_id。

这个设计对二开的影响是:如果你在某个端修改了登录逻辑,要确保服务端的 token 校验机制不受影响。比如在小程序端新增了“一键登录”功能,服务端生成的 token 仍然需要符合统一的格式和有效期规则。

五、二开常见问题与避坑

坑一:环境变量文件没有去掉 .example 后缀

这是 uniapp 开发中最常见的启动失败原因。uniapp/目录下提供的是.env.development.example,必须复制一份并去掉.example后缀,否则编译时会报错“请参考官方文档在 .env 文件下配置请求域名”。

坑二:新增页面忘记注册路由

uni-app 的页面必须在pages.json中注册才能访问。二开时新增页面后,检查pages.jsonpages数组中是否包含了新页面的路径。如果是 tabBar 页面,还需要在tabBar.list中同步配置。

坑三:token 失效后登录页循环跳转

如果登录页本身也需要请求接口(比如获取验证码),当 token 失效时登录页的请求也会返回-1,导致登录页反复跳转自己。解决方案是在请求封装中排除登录相关的接口,或者在登录页的onLoad中先清除失效 token。

坑四:小程序端 wx.login 的 code 复用

wx.login()获取的 code 只能使用一次。如果二开时在多个地方调用了wx.login(),或者把 code 缓存起来多次使用,都会导致登录失败。每次登录都需要重新调用wx.login()获取新的 code

坑五:H5 端刷新后状态丢失

H5 端页面刷新会重置 Pinia 的内存状态。如果 token 只存在 Pinia 的 ref 中而没有持久化到 localStorage,刷新后用户会变成未登录状态。确保 token 和关键用户信息都通过uni.setStorageSync持久化

六、总结

LikeShop 小程序端的工程结构遵循了 uni-app 的标准组织方式:pages/存页面,api/定义接口,utils/request.js封装请求,store/管理登录态

二开时只要记住三条原则:

第一,接口调用走api/。不要在页面中直接写uni.request,所有接口定义集中在api/目录下,便于维护。

第二,token 通过请求封装自动注入。业务页面不需要手动处理 token,请求封装层负责从 Storage 读取并放入请求头。

第三,登录态状态存在 Pinia + Storage 双层。内存状态用于响应式渲染,Storage 用于持久化。页面刷新后从 Storage 恢复。

把这套机制理解清楚,小程序端的二开就会顺畅很多。

本文基于 LikeShop 单商户版 v3.5.1 uniapp 源码及官方开发文档整理,不同版本的文件路径和命名可能略有差异,请以实际源码为准。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询