本文结合完整旅游App实战项目,汇总Uniapp通用基础知识点、开发环境配置、项目搭建、接口封装、页面开发、多端适配全套内容,零基础可直接上手,适配微信小程序、H5、App多端开发,完整复刻实战项目开发流程。
项目实战视频来源:https://www.bilibili.com/video/BV1zA9ZYvE1e?p=22&vd_source=2a6f44e5bd6407acc4cc2bc1d135ccbc
第一部分:Uniapp 通用基础知识
1.1 页面三层结构(每个.vue页面固定三段)
Uniapp 所有页面文件统一分为视图层 template、逻辑层 script、样式层 style,项目全程采用 Vue3 组合式 setup 写法,摒弃 Vue2 繁琐的配置式写法,代码更简洁高效。
完整标准页面模板
<!-- 1.视图层:写页面上能看到的所有内容,等同于HTML --> <template> <!-- uni不能用div、span、p等html标签,必须用小程序规范标签 --> <view class="box"> <!-- view = div 块级容器 --> <text>{{ msg }}</text> <!-- text = span 文字标签,文字必须包在text里 --> </view> </template> <!-- 2.逻辑层:处理数据、接口、点击事件,所有JS代码放这里 --> <script setup> // Vue3组合式API,不用写export default,直接定义变量方法 import { ref } from 'vue' // ref定义响应式文本/数字变量,页面直接插值使用 const msg = ref('巩固uni-app基础') </script> <!-- 3.样式层:控制页面颜色、宽高、边距 --> <style lang="scss" scoped> /* lang="scss" 开启scss语法,支持变量、嵌套样式 */ /* scoped:样式只作用于当前页面,不会污染其他页面 */ .box { padding: 20rpx; } </style>核心注意事项
template 根标签只能有一个,所有页面内容必须包裹在同一个根view中;
禁止使用 div、span、img 等HTML原生标签,统一替换为 view、text、image;
所有文字内容必须写在 text 标签内,否则小程序会出现文字排版错乱、兼容问题。
1.2 rpx适配、全局uni.scss、条件编译
(1)rpx 自适应单位(多屏幕适配核心)
为解决手机屏幕尺寸、分辨率差异问题,Uniapp 专属自适应单位 rpx,是跨端适配的核心方案。
统一规则:750rpx = 设备完整屏幕宽度,行业统一750px设计稿标准;
使用方式:设计图标注多少px,代码直接写多少rpx(例:30px字体→30rpx,350px宽度→350rpx);
底层自动换算:大屏自动放大、小屏自动缩小,全设备展示比例完全一致;
对比px:px为固定像素,大屏显示过小、小屏内容挤压,Uniapp开发优先全部使用rpx。
(2)全局样式 uni.scss
普通scss文件需要每个页面手动@import导入,冗余繁琐;而 uni.scss 是Uniapp项目专属全局样式文件,框架会自动注入到每一个页面、组件的style中,无需手动引入,全局生效。
UI组件库适配规则:若项目使用 uview-plus、uni-ui 等组件库,必须在 uni.scss 第一行引入组件库主题样式,后续自定义变量才能覆盖组件库默认配色。
// 使用uview-plus必加,必须置于uni.scss第一行 @import'@/uni_modules/uview-plus/theme.scss';(3)条件编译(一套代码兼容多端)
Uniapp一套代码可打包微信小程序、H5、App(安卓/iOS),但部分API、样式、语法多端不通用,通过条件编译区分端逻辑,打包时自动删除无关代码,实现多端兼容。
基础语法规则
//#ifdef 端标识:仅当前端生效(if 如果)//#ifndef 端标识:非当前端生效(if not 非)//#endif:条件结束标记,必须成对使用,不可遗漏
常用端标识及场景
#ifdef MP-WEIXIN:仅微信小程序(微信登录、小程序分享)#ifdef H5:仅网页浏览器(鼠标样式、网页专属路由)#ifdef APP-PLUS:仅手机App(原生推送、本地文件操作)#ifndef APP-PLUS:除App外所有端(H5+小程序,屏蔽App不兼容接口)#ifdef VUE3:仅Vue3项目生效
Vue2/Vue3兼容示例
// 当前是vue3项目才执行 //#ifdef VUE3 import { createSSRApp } from 'vue' // #endif // 不是vue3(vue2)才执行 //#ifndef VUE3 import Vue from 'vue' //#endif1.3 常用指令(页面数据绑定核心)
所有指令均书写在 template 标签中,用于快速实现页面数据渲染、交互绑定:
v-for="(item,index) in 列表":循环渲染列表(商品、菜单、景点列表),使用时必须绑定key;
v-if="布尔值":条件渲染,满足条件渲染标签,不满足则直接移除DOM;
v-bind:属性="变量":动态绑定标签属性,简写:属性="变量"(图片地址、动态class);
v-on:事件="方法":绑定交互事件,简写@事件="方法"(@click点击事件);
v-model="变量":表单双向绑定,输入框内容自动同步JS变量数据。
1.4 页面生命周期
页面级生命周期(script setup 中使用)
生命周期 | 触发时机 | 使用场景 |
|---|---|---|
onLoad | 页面加载,仅执行一次 | 页面初始化接口请求、接收路由参数 |
onReachBottom | 页面滚动触底 | 分页加载更多列表数据 |
onPageScroll | 页面滚动实时触发 | 滚动修改导航栏样式、显示/隐藏悬浮按钮 |
App全局生命周期(App.vue中使用)
执行顺序:onLaunch → onShow → onHide
onLaunch:程序首次打开仅执行1次,用于项目初始化、设备信息获取、全局登录校验;
onShow:页面/应用显示时触发;
onHide:页面/应用切后台隐藏时触发。
1.5 路由跳转与页面传参
(1)四种核心跳转API区别
uni.navigateTo():普通页面跳转,保留上一页,自带返回箭头,不可跳转tabBar页面
uni.navigateTo({url:"/pages/goods/goods"})uni.switchTab():专门用于切换底部tabBar页面,跳转后销毁所有非tab页面
uni.switchTab({url:"/pages/mine/mine"})uni.redirectTo():跳转并关闭当前页面,无法返回上一页,适用于登录成功后跳转首页
uni.redirectTo({url:"/pages/index/index"})uni.navigateBack():返回上N级页面,无参数默认返回上一页,delta指定返回层级
uni.navigateBack() // 返回上一页uni.navigateBack({delta:2}) // 一次性退回2页(A→B→C,C直接返回A)
(2)两种页面传参方式
① 普通字符串传参(数字、短文本)
// 跳转页面携带id参数 uni.navigateTo({ url: '/pages/detail/detail?id=1001' }) // 接收页onLoad(options){ console.log(options.id) }② 复杂对象/数组传参(序列化+转码)
对象直接拼接URL会丢失数据,需通过JSON.stringify + encodeURIComponent转义,接收端反向解析
const info = { name:'张三',age:18 } uni.navigateTo({ url: `/pages/detail/detail?data=${encodeURIComponent(JSON.stringify(info))}` }) // 接收页面解析 onLoad((options)=>{ const obj = JSON.parse(decodeURIComponent(options.data)) })1.6 常用内置uni API
uni.showToast:轻提示弹窗,成功/失败文字提示,2秒自动消失;
uni.showModal:确认弹窗,自带取消/确定按钮,用于二次确认操作;
uni.showLoading:加载动画,接口请求时开启,必须搭配 uni.hideLoading() 关闭;
uni.setStorageSync/uni.getStorageSync:同步本地缓存存储/读取数据(存token、用户信息);
uni.login:微信小程序授权登录,获取临时code,用于后端登录校验。
1.7 组件基础
1. easycom自动引入机制
配置easycom后,页面使用自定义组件/UI库组件,无需手动import导入、无需注册,直接书写标签即可使用,极大简化开发。
2. 父子组件通信
父传子:自定义属性绑定 <Child :name="name"/>,子组件通过 defineProps 接收;
子传父:子组件自定义$emit事件派发,父组件通过@事件监听接收;
v-model双向绑定:语法糖简化父子传值,实现数据双向同步。
第二部分:Uniapp 开发环境安装与配置
2.1 HBuilderX安装与常用插件
官网下载HBuilderX 标准版即可满足开发需求;
必备插件(工具 → 插件安装):
uni-app 编译器(核心插件,新建项目自动安装);
scss/sass 编译(支持scss样式语法);
uni-app 真机运行(微信小程序真机调试必备)。
编辑器配置:工具 → 设置 → 编辑器配置,可自定义字体、字号、自动保存、缩进等。
2.2 新建Uniapp项目
文件 → 新建 → 项目 → 选择 uni-app;
自定义项目名称、存储路径;
模板选择Vue3版本(本项目全程使用Vue3组合式语法);
创建完成后自动生成项目基础目录结构。
2.3 运行调试方式
运行方式 | 操作路径 | 说明 |
|---|---|---|
浏览器运行 | 运行 → 运行到浏览器 | H5端开发调试,支持热更新,实时预览 |
微信小程序模拟器 | 运行 → 运行到小程序模拟器 → 微信开发者工具 | 需提前配置开发者工具路径 |
微信真机调试 | 微信开发者工具 → 预览/真机调试 | 手机扫码,真机查看适配效果 |
2.4 微信开发者工具关联配置
下载安装微信开发者工具;
HBuilderX配置路径:工具 → 设置 → 运行配置 → 填写微信开发者工具安装路径;
开发者工具开启服务端口:设置 → 安全设置 → 开启服务端口;
关闭域名校验(开发必备):项目详情 → 本地设置 → 勾选「不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书」。
2.5 项目引入UI组件库(uview-plus)
本项目采用 Vue3 专属的uview-plus组件库,搭配uni-ui实现完整UI布局
安装配置步骤
HBuilderX插件市场搜索uview-plus,直接导入插件到项目;
插件自动安装至 uni_modules/uview-plus/ 目录;
main.js 全局注册组件库:
import uviewPlus from './uni_modules/uview-plus' // Vue3 部分 import { createSSRApp } from 'vue' export function createApp() { const app = createSSRApp(App) app.use(uviewPlus) // 注册 uview-plus return { app } }uni.scss 引入主题变量(置于第一行):
@import '@/uni_modules/uview-plus/theme.scss';pages.json 配置easycom自动引入:
"easycom": { "autoscan": true, "custom": { "^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue", "^up-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue", "^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue" } }
配置完成后,所有uview-plus组件(up-swiper、up-button等)无需手动导入,直接使用标签即可。同时项目引入uni-ui组件库(图标、列表等),同样通过插件市场导入,easycom自动识别。
2.6 核心配置文件基础认识
1. pages.json(页面路由与全局样式配置)
核心作用:注册页面路由、配置tabBar、全局导航栏样式、组件自动引入规则
{ "pages": [ { "path": "pages/index/index", "style": { "navigationBarTitleText": "uni-app" } }, { "path": "pages/detail/detail", "style": { "navigationStyle": "custom" } } ], "tabBar": { "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/like/like", "text": "喜欢" }, { "pagePath": "pages/my/my", "text": "我的" } ] }, "globalStyle": { "navigationBarTextStyle": "black", "navigationBarBackgroundColor": "#F8F8F8" } }2. manifest.json(应用全局配置)
核心配置项:
name:应用名称;
appid:项目唯一标识;
mp-weixin:微信小程序专属配置(appid、域名校验等);
vueVersion:指定Vue版本(本项目为3)。
第三部分:旅游App实战项目开发流程
本项目为零基础入门练手项目,结构清晰、功能完整,主打熟悉Uniapp+Vue3开发语法、接口请求、多端适配、页面交互逻辑,适合新手实战练手。
3.1 项目整体结构与页面规划
项目目录结构
旅游uniapp/ ├── api/ # 接口层 │ ├── api.js # 接口地址定义 │ ├── http.js # 请求封装 │ ├── mock.js # Mock 拦截入口 │ └── mockData/ │ └── pageApi.js # Mock 数据 ├── pages/ # 页面 │ ├── index/index.vue # 首页(轮播图+瀑布流) │ ├── detail/detail.vue # 详情页 │ ├── line/line.vue # 游玩线路页(地图+推荐) │ ├── like/like.vue # 喜欢页 │ └── my/my.vue # 我的页(登录+个人信息) ├── static/ # 静态资源 │ └── tabbar/ # tabBar 图标 ├── uni_modules/ # 插件目录(uview-plus 等) ├── App.vue # 应用入口 ├── main.js # 主入口 ├── pages.json # 路由与全局配置 ├── manifest.json # 应用配置 ├── uni.scss # 全局样式变量 └── package.json # 依赖管理页面功能规划
页面 | 路径 | 页面类型 | 核心功能 |
|---|---|---|---|
首页 | pages/index/index | tabBar主页面 | 搜索、轮播图、景点瀑布流列表、触底加载 |
喜欢页 | pages/like/like | tabBar主页面 | 收藏景点卡片列表展示 |
我的页 | pages/my/my | tabBar主页面 | 微信授权登录、用户信息展示、功能列表 |
详情页 | pages/detail/detail | 普通跳转页 | 景点详情、游玩推荐、跳转线路页 |
线路页 | pages/line/line | 普通跳转页 | 地图定位、景点评分、横向推荐列表 |
tabBar完整配置
{ "pages": [ { "path": "pages/index/index", "style": { "navigationBarTitleText": "uni-app" } }, { "path": "pages/like/like", "style": { "navigationBarTitleText": "" } }, { "path": "pages/my/my", "style": { "navigationBarTitleText": "" } }, { "path": "pages/detail/detail", "style": { "navigationBarTitleText": "", "navigationStyle": "custom" } }, { "path": "pages/line/line", "style": { "navigationBarTitleText": "" } } ], "tabBar": { "color": "#7A7E83", "selectedColor": "#2867CE", "list": [ { "pagePath": "pages/index/index", "iconPath": "static/tabbar/ly-home01.png", "selectedIconPath": "static/tabbar/ly-home.png", "text": "首页" }, { "pagePath": "pages/like/like", "iconPath": "static/tabbar/ly-link01.png", "selectedIconPath": "static/tabbar/ly-link.png", "text": "喜欢" }, { "pagePath": "pages/my/my", "iconPath": "static/tabbar/ly-my01.png", "selectedIconPath": "static/tabbar/ly-my.png", "text": "我的" } ] } }3.2 项目全局样式与UI组件全局引入
uni.scss 全局样式配置
仅存放全局scss变量,不写具体样式,避免全局注入冗余代码、增大包体积
@import '@/uni_modules/uview-plus/theme.scss';main.js 全局注册配置
全局注册UI组件库、引入Mock模拟数据
import App from './App' import uviewPlus from './uni_modules/uview-plus' import './api/mock.js' // 引入 Mock 数据拦截 // Vue3 import { createSSRApp } from 'vue' export function createApp() { const app = createSSRApp(App) app.use(uviewPlus) // 全局注册 uview-plus return { app } }pages.json easycom配置
配置后所有uview-plus组件无需手动导入,直接使用标签开发
"easycom": { "autoscan": true, "custom": { "^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue", "^up-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue", "^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue" } },3.3 网络请求写法与数据请求调用
1. 全局请求封装(api/http.js)
基于Promise封装uni.request,统一处理请求头、响应拦截、错误提示,支持async/await
let baseUrl = '' // 通过环境判断接口地址 if (process.env.NODE_ENV === 'development') { baseUrl = 'https://m1.apifoxmock.com/m1/4728220-0-default/api' } else { baseUrl = 'https://m1.apifoxmock.com/m1/4728220-0-default/api' } export default function http(url, data = {}, method = 'GET') { return new Promise((resolve, reject) => { uni.request({ url: baseUrl + url, data, method, header: { 'token': uni.getStorageSync('token') || '' // 自动携带 token }, success: res => { if (res.statusCode == 200) { if (res.data.code == 1) { resolve(res.data.data) // 成功返回 data } else if (res.data.code == 0) { uni.showToast({ title: res.data.msg, icon: 'none' }) reject(res.data.msg) // 业务错误提示 } } }, fail: () => { uni.showToast({ title: '服务器请求异常', icon: 'none' }) } }) }) }2. 接口统一管理(api/api.js)
所有接口统一封装,便于维护和修改
import http from './http.js' export const getBanner = () => { return http('/user/getBanner') } export const getHomeList = () => { return http('/user/getHomeList') } export const login = (code) => { return http('/login', { code }, 'POST') } export const getUserInfo = () => { return http('/getUserInfo') } export const detailProject = () => { return http('/detail/project') } export const projectInfo = (id) => { return http('/project/info', id) } export const likeList = () => { return http('/like/list') }3. 页面接口调用实战
所有页面数据请求统一在 onLoad 生命周期中执行
// 首页 — 加载轮播图和列表 onLoad(() => { getBanner().then(res => { bannerList.value = res.bannerList }) getHomeList().then(res => { flowList.value = res }) }) // 喜欢页 onLoad(() => { likeList().then(res => { linkList.value = res }) }) // 详情页 — 加载游玩项目推荐 onLoad((opt) => { detailProject().then(res => { projectList.value = res }) details.dt = JSON.parse(decodeURIComponent(opt.item)) }) // 线路页 — 通过 id 请求项目详情 onLoad((props) => { projectInfo({ id: props.id }).then(res => { detailInfo.value = res }) }) // 我的页 — 登录后获取用户信息 uni.login({ success: async (data) => { const { token } = await login(data.code) uni.setStorageSync('token', token) const { avatarUrl, nickName } = await getUserInfo() } })4. Mock数据模拟(开发调试)
开发阶段通过Mock.js拦截接口请求,返回模拟数据,无需后端接口即可调试页面
api/mock.js(拦截配置)
// api/mock.js import Mock from "mockjs" import pageApi from './mockData/pageApi' Mock.mock(/api\/user\/getBanner/, 'get', pageApi.getBanner)api/mockData/pageApi.js(模拟数据)
// api/mockData/pageApi.js — 返回模拟的轮播图和列表数据 export default { getBanner: () => { return { code: 1, data: { bannerList: [...] }, msg: '' } }, getHomeList: () => { return { code: 1, data: [...], msg: '' } } }3.4 页面跳转与传参实战
1. 首页→详情页(复杂对象传参)
// 发送方:将对象序列化后 encodeURIComponent const goDetail = (item) => { const can = JSON.stringify(item) uni.navigateTo({ url: `/pages/detail/detail?item=${encodeURIComponent(can)}` }) } // 接收方:反序列化 decodeURIComponent onLoad((opt) => { details.dt = JSON.parse(decodeURIComponent(opt.item)) })核心原因:URL不支持{}、""等特殊字符,必须通过转义工具处理,避免数据丢失、解析异常。
2. 详情页→线路页(简单ID传参)
// 发送方 const goLine = (item) => { uni.navigateTo({ url: `/pages/line/line?id=${item.id}` }) } // 接收方 onLoad((props) => { projectInfo({ id: props.id }).then(res => { detailInfo.value = res }) })3.5 各页面布局与交互实现思路
1. 首页(搜索+轮播+瀑布流列表)
核心功能:搜索框、轮播图、公告栏、瀑布流景点列表、触底加载、回到顶部
<template> <!-- 搜索栏 --> <up-search placeholder="搜索景点" v-model="keyword" /> <!-- 轮播图 --> <up-swiper keyName="image" showTitle :list="bannerList" :autoplay="true" height="160" /> <!-- 公告栏 --> <up-notice-bar text="项目数据仅为示例,非真实数据" /> <!-- 瀑布流列表 --> <up-waterfall v-model="flowList" ref="uWaterfallRef"> <template v-slot:left="{ leftList }"> <view v-for="(item, index) in leftList" :key="index" @click="goDetail(item)"> <up-lazy-load :image="item.img" /> <view>{{ item.title }}</view> <view>{{ item.times }}</view> <view class="demo-tag"> <view>{{ item.tag[0] }}</view> <view>{{ item.tag[1] }}</view> </view> <view v-if="item.isDot">{{ item.isDot }}</view> </view> </template> <template v-slot:right="{ rightList }"> <!-- 同 left 结构 --> </template> </up-waterfall> <!-- 回到顶部按钮 --> <view v-if="showTopBtn" @click="Totop"> <up-icon name="arrow-upward" /> </view> </template>核心交互:onReachBottom触底加载更多、onPageScroll监听滚动显示/隐藏回到顶部按钮、图片懒加载优化性能。
2. 线路页(地图+评分+横向滚动)
<!-- 地图组件 --> <map v-if="detailInfo.id" :markers="detailInfo.markers" :latitude="detailInfo.location[0]" :longitude="detailInfo.location[1]" :show-scale="true" /> <!-- 评分 --> <up-rate :count="count" v-model="detailInfo.count" active-color="#f56c6c" /> <!-- 横向滚动推荐 --> <up-scroll-list :indicator="true"> <view v-for="(item, index) in detailInfo.other"> <image :src="item.url" /> <view>{{ item.name }}</view> </view> </up-scroll-list>3. 喜欢页(两列卡片列表)
<view class="tj-list"> <view class="item" v-for="(item, index) in linkList" :key="index"> <image :src="item.img" mode="aspectFill" /> <view class="topFixed">喜欢</view> <view class="tit">{{ item.title }}</view> <text class="text">{{ item.introduce }}</text> </view> </view>核心样式:flex两列自适应布局,文字超出两行省略,优化页面展示效果。
4. 我的页(登录弹窗+用户信息)
<!-- 顶部弧形背景 + 用户信息卡 --> <view class="topBox"> <view class="users" @click="setFun"> <template v-if="!userInfo.nickName"> <image src="../../static/tt.jpg" /> <view>注册/登录</view> </template> <template v-else> <image :src="userInfo.avatarUrl" /> <view>{{ userInfo.nickName }}</view> </template> </view> </view> <!-- 功能列表 --> <uni-list> <uni-list-item :show-extra-icon="true" :extra-icon="extraIcon1" showArrow title="个人信息" /> <uni-list-item :show-extra-icon="true" :extra-icon="extraIcon2" showArrow title="我的购物车" /> </uni-list> <!-- 登录弹窗 --> <up-popup :show="show" @close="close"> <view class="popup"> <view>获取您的头像和昵称</view> <Button open-type="chooseAvatar" @chooseavatar="onChooseavatar"> <img :src="userInfo.avatarUrl" /> </Button> <input @input="changName" type="nickname" /> <button @click="userSubmit">确定</button> </view> </up-popup>核心逻辑:微信授权登录、头像昵称获取、弹窗自定义编辑用户信息、本地缓存存储登录状态。
3.6 完整项目开发流程总结
整套旅游小程序项目标准化开发流程,新手可直接复用:
需求分析:确定5个核心页面及对应功能,梳理页面跳转逻辑;
项目搭建:HBuilderX新建Vue3版本Uniapp项目,配置pages.json路由、tabBar导航;
UI环境搭建:导入uview-plus、uni-ui组件库,配置easycom自动引入、全局样式;
网络层封装:全局http请求封装、统一接口管理、Mock模拟数据配置;
逐页功能开发:依次开发首页、详情页、线路页、喜欢页、我的页,完成布局、接口、交互;
联调测试:浏览器、小程序模拟器调试,打印日志排查接口、渲染问题;
Bug修复:适配多端样式、接口异常、传参报错等问题;
多端验证:完成H5、微信小程序、App多端适配验证。
本总结仅为个人学习复盘,仅供自身学习存档和新手参考,若内容存在错误、不合理或可优化的地方,恳请各位大佬、前辈多多指正,我会及时修正、查漏补缺,持续精进自身的开发能力!