一、项目构建与调试
项目构建分别为两种
1、基于和buildx构建
打开 hbuildx -> 新建 -> 项目 -> uni-app ->选择一个模板 (直接推荐 默认)
你会得到如下项目结构:
- pages - 存放所有页面文件的目录,每个页面通常包含 .vue 文件
- static - 静态资源目录,用于存放图片、字体等静态文件
- App.vue - 应用的根组件,定义应用的全局生命周期和样式
- main.js - 应用的入口文件,用于初始化 Vue 实例和全局配置
- manifest.json - 应用的配置文件,包含应用名称、图标、版本等信息,特别是打包 App 时的配置
- pages.json - 页面路由配置文件,定义页面路径、窗口样式、tabBar 等
- shime-uni.d.ts - TypeScript 类型声明文件,为 uni-app API 提供类型定义
- uni.scss - 全局样式变量文件,可定义全局使用的样式变量
2、使用npx 直接创建 (推荐)
npx degit dcloudio/uni-preset-vue#vite my-project
详细网址如下 uni-app官网
项目结构如下图所示:
npx构建的项目相对于hubuildx 创建的项目来说提供了更多便利
1、 package.json
(uni的基础依赖)、
vite.config(构建工具配置)、
.gitignore自动集成到里面
2、可以使用其他代码编辑器直接开发
直接用npm run dev:h5 就可以直接吧项目跑起来了相对hbuildx更方便
3、更适合复杂项目
对于大型项目,命令行方式通常更灵活
可以更好地管理复杂的依赖关系
4、更好的定制性
可以更方便地修改 Vite 配置
更容易集成第三方工具和库
更容易自定义构建流程.
界面预览&调试
1、h5 、app
hbuilder开发过程中选择运行 -> 然后可以选择内置浏览器 或者直接运行到浏览器
在npx构建项目中可以 直接 npm run dev:h5 但是具体看 package.json 的配置;
关于数据请求渲染页面相关在 开发配置项中介绍
2、微信小程序调试
微信小程序的界面调试相对来说麻烦点 当然可以用测试appId
关于appId 的申请 :
1、微信公众平台:微信公众平台 打开后账号分类选择“小程序”
2、点击注册按钮,进入小程序注册步骤 注册
3、进行信息登记 完善信息等等
相关请参考
第一步appid
1、配置appId 用hbuilder 打开 manifest.json 选择微信小程序配置
2、或者直接在manifest.json —— "mp-weixin"中填写
第二步 配置运行微信开发者工具
hbuilder 中
npx 项目中
就可以了
第三步 跑起来就完了
二、开发 配置项 注意事项
1、路由导航栏篇
“easycom” 粗俗点 全局组件引用 简化组件的引用方式 不需要手动导入组件的情况下直接在模板中使用组件
“pages” 粗俗点可以直接看成 路由配置 当然其中包含了 “path” 页面路径、 "style"导航栏配置
“tabBar” 底部路由导航栏
其中可以稍微注意一下一些默认属性 比如在写移动端的时候 经常要计算内容区的高度
export const getNavHeight = () => {
const systemInfo = uni.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight; // 状态栏高度
const navBarHeight = 44; // 导航栏固定高度
const tabBarHeight = 50; // 底部导航栏固定高度
return {
allHeight: statusBarHeight + navBarHeight + tabBarHeight,
topHeight: statusBarHeight + navBarHeight
};
};
2、页面篇
html
页面代码相对没那么多注意的东西就正常的看成 .vue 文件就好了
当然我们在开发过程中也要注意适配
比如 在html中:div-> view 、 img->image 、 span->text 适当进行统一 这样在运行小程序或者打包的时候能避免大多数问题
图片方面 移动端尽量少使用本地图片 要想直接适配小程序 要保证打包后体积小于2m 所以图片能用请求就用请求 当然 不兼容小程序无所谓
推荐比较好用一点的form 表单组件 uni-form 个人觉得还行 经过全局引入之后可以直接使用
代码示例
//安装 npm install @dcloudio/uni-ui
//全局注册 pages.json中
"easycom": {
"autoscan": true,
"custom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
}
}
<template>
<div class="edit-class-container">
<uni-forms ref="form" :model="formData" :rules="rules">
<uni-forms-item label="班次名称" name="className">
<uni-easyinput v-model="formData.className" placeholder="请输入班次名称" />
</uni-forms-item>
<uni-forms-item label="上班时间" name="startTime">
<view class="time-picker" @click="openTimePicker('start')">
<text>{{ formData.startTime || '请选择上班时间' }}</text>
<text class="picker-icon">></text>
</view>
</uni-forms-item>
<uni-forms-item label="下班时间" name="endTime">
<view class="time-picker" @click="openTimePicker('end')">
<text>{{ formData.endTime || '请选择下班时间' }}</text>
<text class="picker-icon">></text>
</view>
</uni-forms-item>
</uni-forms>
<button class="submit-btn" @click="handleSubmit">确定</button>
</div>
<!-- 时间选择器弹窗 -->
<uni-popup ref="popup" type="bottom">
<view class="picker-container">
<view class="picker-header">
<text @click="cancelPicker">取消</text>
<text @click="confirmPicker">确定</text>
</view>
<picker-view class="picker-view" :value="timeArray" @change="handleTimeChange">
<picker-view-column>
<view class="item" v-for="(hour, index) in hours" :key="index">{{ hour }}</view>
</picker-view-column>
<picker-view-column>
<view class="item" v-for="(minute, index) in minutes" :key="index">{{ minute }}</view>
</picker-view-column>
</picker-view>
</view>
</uni-popup>
</template>
const form = ref(null);
const popup = ref(null);
const formData = ref({
className: '',
startTime: '',
endTime: ''
});
// ....
在样式上
尽量不要去使用 background-image这个东西(在小程序中没用)
其他样式基本上正常写
路由跳转
uni.navigateTo(OBJECT) :保留当前页面,跳转到应用内的某个页面
新页面会入栈,可以通过navigateBack返回
页面栈最多十层,超出会报错
保留当前页面状态
适用:详情页面跳转、表单页面跳转、需要返回的页面跳转
uni.redirectTo(OBJECT):关闭当前页面,跳转到应用内的某个页面
适用: 登录成功后跳转、完成某个流程后跳转、不需要返回的场景
uni.reLaunch(OBJECT):关闭所有页面,打开到应用内的某个页面
适用: 回到首页、切换用户身份后刷新应用、需要重置应用状态的场景
uni.switchTab(OBJECT):跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
适用场景:底部标签页切换、从非tabBar页面跳转到tabBar页面
uni.navigateBack(OBJECT):关闭当前页面,返回上一页面或多级页面
路由传参 不一一列举详细请看文档
// 发送参数
uni.navigateTo({
url: 'pages/detail/detail?id=1&type=news&title=标题'
});
// 接收参数
export default {
onLoad(options) {
console.log(options.id); // 1
console.log(options.type); // news
console.log(options.title); // 标题
}
}
生命周期
onLoad 路由传参在接收 、初始化数据
onshow 一般用于更新页面数据等 开始动画
onHide 隐藏 结束动画 清除定时器 延时器
onUnload 页面被销毁,内存被回收
性能优化方面
使用onLoad缓存不常变化的数据
在onHide中暂停不必要的操作
使用onShow进行按需刷新而非全量刷新
3、数据请求篇
import {
// toast,
getStorageSync
// toLoginPage
} from './utils.js';
import { HTTP_REQUEST_URL, TOKENNAME } from './config.js';
const baseRequest = async (
url,
method,
data,
header,
enableChunked = false,
loading = false,
needToken = true
) => {
let token = await uni.getStorageSync('TOKEN');
// console.log("请求参数", url, method, data, header, token)
let headerBase = header
? header
: {
'Content-Type': 'application/json'
};
if (token && needToken && !url.includes('/map-api')) {
headerBase[TOKENNAME] = token;
}
return new Promise((reslove, reject) => {
loading &&
uni.showLoading({
title: 'loading'
});
uni.request({
url: url.includes('/map-api') ? url : HTTP_REQUEST_URL + url,
method: method || 'GET',
header: headerBase,
timeout: 200000,
data: data || {},
enableChunked: enableChunked,
success: (res) => {
// console.log("res===", res)
if (res.data.code === 401) {
uni.showModal({
title: '提示',
content: '身份过期请重新登录',
success: function (res) {
if (res.confirm) {
uni.clearStorageSync();
uni.redirectTo({
url: '/pages/login/index'
});
reslove(res.data);
} else if (res.cancel) {
console.log('用户点击取消');
reslove(res.data);
// 用户点击了取消按钮的相关逻辑可以放在这里
}
}
});
} else {
reslove(res.data);
}
},
fail: (msg) => {
reject(msg);
},
complete: () => {
loading && uni.hideLoading();
}
});
});
};
const request = {};
['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {
request[method] = (api, data, header, enableChunked, loading, needToken) =>
baseRequest(api, method, data, header, enableChunked, loading, needToken);
});
export default request;
这份数据请求能应对绝大多数场景
小程序中
使用uni.request()替代传统的fetch或axios
请求域名必须在小程序管理后台配置白名单
默认不支持跨域请求,所有请求都需要HTTPS
不大支持复杂的类型请求 比如formData 等要做适配 后面我会贴代码上来
其他都比较快乐
好的,请提供当前页面的内容和排版样式,我会帮您进行优化。
4、其他
要注意权限处理 比如
用户授权
位置、相机、相册等需要用户授权
使用uni.authorize()或相关API获取权限
需要处理用户拒绝授权的情况
我这里举例一下位置授权配置
1、位置权限
manifest.json 中
"h5": {
"template": "index.html",
"router": {
"mode": "hash",
"base": "/h5/"
},
"publicPath": "/h5/",
"sdkConfigs": {
"maps": {
"tencent": {
"key": "J7DBZ-QQCKX-R2L4Z-T5YVE-QMS4J-NLBVP"
},
"qqmap": {
"key": "J7DBZ-QQCKX-R2L4Z-T5YVE-QMS4J-NLBVP"
},
"geolocation": {
"type": "tencent",
"coordinate": "gcj02"
}
}
},
"domain": "https://anneng.xaxcsz.com",
"devServer": {
"https": false,
"port": 8080,
"disableHostCheck": true
}
},
页面中
const getPosition = () => {
return new Promise((resolve, reject) => {
uni.getLocation({
type: 'gcj02', // 使用国测局坐标系(火星坐标系)
success: async (res) => {
try {
// 获取位置成功
const locationInfo = {
latitude: res.latitude,
longitude: res.longitude,
accuracy: res.accuracy || 1000
};
resolve(locationInfo);
} catch (error) {
reject(new Error(`处理位置信息失败: ${error.message || '未知错误'}`));
}
},
fail: (err) => {
uni.showToast({
title: '获取位置失败',
icon: 'none',
duration: 2000
});
reject(new Error(`获取位置失败: ${err.errMsg || '未知错误'}`));
}
});
});
};
// 修改权限检查和位置获取逻辑
const getPersonalLocation = async () => {
// 直接尝试获取位置,不使用 uni.authorize
const position = await getPosition();
console.log('position1', position);
personalLocation.value = {
...position,
latitude: position.latitude,
longitude: position.longitude,
address: position.address || position.province + position.city + (position.district || '')
};
position.value = personalLocation.value.address;
....
};
当然有更多的东西 后面会在文档中补充
登录等 比如手机号 微信授权啥的请看文档
三、语法限制
1. 小程序特有语法
小程序开发中存在一些语法限制和特殊规则:
- 数据绑定:使用{{}}进行数据绑定,而不是Vue中的v-text
- 条件渲染:使用v-if、v-else-if、v-else,与Vue类似
- 列表渲染:使用v-for="(item, index) in items",需要指定:key
- 事件处理:使用@tap代替@click,在移动端更符合触摸操作
2. 样式限制
- 选择器限制:不支持通配符*和属性选择器
- 单位使用:推荐使用rpx作为尺寸单位,可自适应不同屏幕
- 样式隔离:每个组件样式默认隔离,需使用特定方式实现全局样式
- 不支持部分CSS特性:如:hover伪类在某些平台可能不生效
3. JS API限制
- DOM操作限制:不能直接操作DOM,需使用小程序提供的API
- 网络请求限制:只能请求已配置的安全域名
- 异步API:大多数API都是异步的,需要使用回调或Promise处理
四、小程序与其他区别
1. 与Web开发的区别
- 运行环境不同:小程序运行在特定容器中,非标准浏览器环境
- 页面结构不同:使用.vue单文件组件,包含模板、脚本和样式
- 生命周期不同:有特定的应用和页面生命周期函数
- API差异:使用平台提供的API,而非Web标准API
2. 与原生App的区别
- 性能差异:性能介于Web和原生App之间
- 开发效率:开发效率高于原生App,一套代码多端运行
- 功能限制:部分硬件功能和系统API访问受限
- 发布流程:需要通过平台审核,更新周期较长
3. 与H5的区别
- 离线能力:小程序可以离线使用部分功能
- 系统能力:可以调用更多设备和系统能力
- 用户体验:交互更接近原生应用,体验更流畅
- 分发方式:通过应用商店或扫码方式获取,而非浏览器访问
五、打包
h5
更具配置项npm run build:h5 就好了(推荐)没有什么流程 简单方便
或者直接在 hbuilder 点击发行 pc或者h5 即可 其他的按照流程来
微信小程序
这个相对简单,申请好appId 准备好域名 接口白名单 之类的东西 写好代码
在微信开发者工具中有发行和版本管理
app(目前我只试过安卓apk)
首先生成证书
安装JRE环境 Java Downloads | Oracle 选择对应操作系统
选择自己要的版本 然后注册或者直接登录
配置环境变量
d:
set PATH=%PATH%;"C:\Program Files\Java\jre1.8.0_201\bin"
生成签名证书
keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystore
Enter keystore password: //输入证书文件密码,输入完成回车
Re-enter new password: //再次输入证书文件密码,输入完成回车
What is your first and last name?
[Unknown]: //输入名字和姓氏,输入完成回车
What is the name of your organizational unit?
[Unknown]: //输入组织单位名称,输入完成回车
What is the name of your organization?
[Unknown]: //输入组织名称,输入完成回车
What is the name of your City or Locality?
[Unknown]: //输入城市或区域名称,输入完成回车
What is the name of your State or Province?
[Unknown]: //输入省/市/自治区名称,输入完成回车
What is the two-letter country code for this unit?
[Unknown]: //输入国家/地区代号(两个字母),中国为CN,输入完成回车
Is CN=XX, OU=XX, O=XX, L=XX, ST=XX, C=XX correct?
[no]: //确认上面输入的内容是否正确,输入y,回车
Enter key password for <testalias>
(RETURN if same as keystore password): //确认证书密码与证书文件密码一样(HBuilder|HBuilderX要求这两个密码一致),直接回车就可以
查看证书信息
keytool -list -v -keystore test.keystore
Enter keystore password: //输入密码,回车
详情请参考 Android平台签名证书(.keystore)生成指南 - DCloud问答
第二步 点击发行 选择云打包 然后 填入证书信息 就ok了
Android 自定义渠道包 Android 自定义渠道包 | uni-app官网
我也在慢慢学习中 后续更新....
六、补充篇原生微信小程序
原生微信小程序由四种文件组成:
- .js:脚本文件
- .wxml:模板文件(类似HTML)
- .wxss:样式文件(类似CSS)
- .json:配置文件