一、项目构建与调试
项目构建分别为两种
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: 页面样式配置,包括导航栏、下拉刷新等
app-plus->titleNView 可以配置头部导航栏按钮内容等
globalStyle:全局样式配置(导航栏文字颜色、背景色;页面背景色等)
tabBar:底部导航栏配置
color: 未选中颜色
selectedColor: 选中颜色
list: 导航项配置
导航栏高度计算
其中可以稍微注意一下一些默认属性 比如在写移动端的时候 经常要计算内容区的高度
iOS:状态栏高度为 44px(iPhone X 及以上机型为 88px,包含安全区域)
Android:状态栏高度通常为 24px-28px,但各厂商可能有差异
微信小程序:导航栏高度固定为 44px,状态栏高度需动态获取
H5:通常没有原生导航栏,可自定义高度
// 获取导航栏高度
const getNavBarHeight = () => {
const systemInfo = uni.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight;
// 导航栏高度
let navBarHeight = 44; // 默认值
// #ifdef MP-WEIXIN
navBarHeight = 44; // 微信小程序固定值
// #endif
// #ifdef APP-PLUS
navBarHeight = systemInfo.platform === 'ios' ? 44 : 48;
// #endif
// 总高度 = 状态栏 + 导航栏
const totalHeight = statusBarHeight + navBarHeight;
const tabBarHeight = 50; // 底部导航栏固定高度
return {
statusBarHeight,
navBarHeight,
tabBarHeight,
totalHeight
};
};
2、页面篇
html
页面代码相对没那么多注意的东西就正常的看成 .vue 文件就好了
当然我们在开发过程中也要注意适配
比如 在html中:div-> view 、 img->image 、 span->text 适当进行统一 这样在运行小程序或者打包的时候能避免大多数问题
图片方面 移动端尽量少使用本地图片 要想直接适配小程序 微信小程序主包限制为20MB,分包每个不超过2MB 所以图片能用请求就用请求 当然 不兼容小程序无所谓
推荐比较好用一点的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;
这份数据请求能应对绝大多数场景
请求域名必须在小程序管理后台配置白名单
默认不支持跨域请求,所有请求都需要HTTPS
在uni-app中,我们不需要引入fetch或axios等第三方库,因为uni.request已经提供了完善的网络请求功能。然而,对于stream流式请求这种特殊场景,uni.request需要额外的配置才能正确处理。
// http: stream流式请求
// #ifdef MP-WEIXIN || app
export const chatFetch = async (data) => {
let token = await uni.getStorageSync("TOKEN");
let memberToken = await uni.getStorageSync("MEMBER_TOKEN");
let headerBase = {
"Content-Type": 'application/json',
"AppId": data.app_id
}
if (token) {
let requsetToken = memberToken ? memberToken : token
headerBase[TOKENNAME] = JSON.parse(requsetToken);
}
const task = uni.request({
url: `${HTTP_CHATAPI}/chatStream/stream/chat_new`,
method: "POST",
// responseType:"text",
enableChunked: true, //注意配置
// enableHttp2:true,
data: data,
header: headerBase,
success: (res) => {},
fail: (err) => {},
complete: () => {}
})
return task
}
// #endif
// #ifdef H5
export const chatFetch = async (data) => {
let token = await uni.getStorageSync("TOKEN");
let memberToken = await uni.getStorageSync("MEMBER_TOKEN");
// 在页面中创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest()
// 设置 XMLHttpRequest 对象
xhr.open('POST', `${HTTP_CHATAPI}/chatStream/stream/chat_new`)
xhr.responseType = 'text'
xhr.timeout = 0
xhr.setRequestHeader("Content-Type", 'application/json');
xhr.setRequestHeader("AppId", data.app_id);
if (token) {
let requsetToken = memberToken ? memberToken : token;
xhr.setRequestHeader(`${TOKENNAME}`, JSON.parse(requsetToken));
}
let previousResponseText = null;
let onChunkReceivedCb = null;
xhr.onChunkReceived = function(fn) {
onChunkReceivedCb = function() {
fn.apply(this, arguments)
}
return onChunkReceivedCb
}
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.LOADING || xhr.readyState === XMLHttpRequest.DONE) {
// 处理 HTTP 数据块
// console.log("xhr.responseText", xhr.responseText)
if(xhr.onChunkReceived) {
let resText = xhr.responseText;
if(previousResponseText) {
let index = xhr.responseText.indexOf(previousResponseText);
if(index != -1) {
resText = resText.substring(index + previousResponseText.length)
}
}
previousResponseText = xhr.responseText;
onChunkReceivedCb(resText)
}
}
}
xhr.send(JSON.stringify(data))
return xhr
}
// #endif
// #ifdef H5 这是使用fetch请求 暂时没验证过仅供参考
export const chatFetch = async (data) => {
let token = await uni.getStorageSync("TOKEN");
let memberToken = await uni.getStorageSync("MEMBER_TOKEN");
// 构建请求头
const headers = {
"Content-Type": 'application/json',
"AppId": data.app_id
};
if (token) {
let requestToken = memberToken ? memberToken : token;
headers[TOKENNAME] = JSON.parse(requestToken);
}
// 使用Fetch API进行流式请求
const response = await fetch(`${HTTP_CHATAPI}/chatStream/stream/chat_new`, {
method: 'POST',
headers: headers,
body: JSON.stringify(data)
});
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 获取响应的ReadableStream
const reader = response.body.getReader();
const decoder = new TextDecoder();
// 创建一个控制器对象,用于管理流
const controller = {
reader,
decoder,
// 读取数据块的方法
async read(callback) {
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
callback(null, true); // 传递null表示结束
break;
}
// 解码二进制数据为文本
const chunk = decoder.decode(value, { stream: true });
callback(chunk, false);
}
} catch (error) {
console.error('Stream reading error:', error);
throw error;
}
},
// 取消流的方法
cancel() {
reader.cancel();
}
};
return controller;
};
// #endif
不大支持复杂的类型请求 比如formData 等要做适配
// import mimeMap from '@/utils/mimeMap.js'
const mimeMap = {
"0.001": "application/x-001",
"0.323": "text/h323",
"0.907": "drawing/907",
".acp": "audio/x-mei-aac",
".aif": "audio/aiff",
".aiff": "audio/aiff",
".asa": "text/asa",
".asp": "text/asp",
".au": "audio/basic",
".awf": "application/vnd.adobe.workflow",
".bmp": "application/x-bmp",
".c4t": "application/x-c4t",
".cal": "application/x-cals",
".cdf": "application/x-netcdf",
".cel": "application/x-cel",
".cg4": "application/x-g4",
".cit": "application/x-cit",
".cml": "text/xml",
".cmx": "application/x-cmx",
".crl": "application/pkix-crl",
".csi": "application/x-csi",
".cut": "application/x-cut",
".dbm": "application/x-dbm",
".dcd": "text/xml",
".der": "application/x-x509-ca-cert",
".dib": "application/x-dib",
".doc": "application/msword",
".drw": "application/x-drw",
".dwf": "Model/vnd.dwf",
".dwg": "application/x-dwg",
".dxf": "application/x-dxf",
".emf": "application/x-emf",
".ent": "text/xml",
".eps": "application/x-ps",
".etd": "application/x-ebx",
".fax": "image/fax",
".fif": "application/fractals",
".frm": "application/x-frm",
".gbr": "application/x-gbr",
".gif": "image/gif",
".gp4": "application/x-gp4",
".hmr": "application/x-hmr",
".hpl": "application/x-hpl",
".hrf": "application/x-hrf",
".htc": "text/x-component",
".html": "text/html",
".htx": "text/html",
".ico": "image/x-icon",
".iff": "application/x-iff",
".igs": "application/x-igs",
".img": "application/x-img",
".isp": "application/x-internet-signup",
".java": "java/*",
".jpe": "image/jpeg",
".jpeg": "image/jpeg",
".jpg": "application/x-jpg",
".jsp": "text/html",
".lar": "application/x-laplayer-reg",
".lavs": "audio/x-liquid-secure",
".lmsff": "audio/x-la-lms",
".ltr": "application/x-ltr",
".m2v": "video/x-mpeg",
".m4e": "video/mpeg4",
".man": "application/x-troff-man",
".mdb": "application/msaccess",
".mfp": "application/x-shockwave-flash",
".mhtml": "message/rfc822",
".mid": "audio/mid",
".mil": "application/x-mil",
".mnd": "audio/x-musicnet-download",
".mocha": "application/x-javascript",
".mp1": "audio/mp1",
".mp2v": "video/mpeg",
".mp4": "video/mpeg4",
".mpd": "application/vnd.ms-project",
".mpeg": "video/mpg",
".mpga": "audio/rn-mpeg",
".mps": "video/x-mpeg",
".mpv": "video/mpg",
".mpw": "application/vnd.ms-project",
".mtx": "text/xml",
".net": "image/pnetvue",
".nws": "message/rfc822",
".out": "application/x-out",
".p12": "application/x-pkcs12",
".p7c": "application/pkcs7-mime",
".p7r": "application/x-pkcs7-certreqresp",
".pc5": "application/x-pc5",
".pcl": "application/x-pcl",
".pdf": "application/pdf",
".pdx": "application/vnd.adobe.pdx",
".pgl": "application/x-pgl",
".pko": "application/vnd.ms-pki.pko",
".plg": "text/html",
".plt": "application/x-plt",
".png": "application/x-png",
".ppa": "application/vnd.ms-powerpoint",
".pps": "application/vnd.ms-powerpoint",
".ppt": "application/x-ppt",
".prf": "application/pics-rules",
".prt": "application/x-prt",
".ps": "application/postscript",
".pwz": "application/vnd.ms-powerpoint",
".ra": "audio/vnd.rn-realaudio",
".ras": "application/x-ras",
".rdf": "text/xml",
".red": "application/x-red",
".rjs": "application/vnd.rn-realsystem-rjs",
".rlc": "application/x-rlc",
".rm": "application/vnd.rn-realmedia",
".rmi": "audio/mid",
".rmm": "audio/x-pn-realaudio",
".rms": "application/vnd.rn-realmedia-secure",
".rmx": "application/vnd.rn-realsystem-rmx",
".rp": "image/vnd.rn-realpix",
".rsml": "application/vnd.rn-rsml",
".rtf": "application/msword",
".rv": "video/vnd.rn-realvideo",
".sat": "application/x-sat",
".sdw": "application/x-sdw",
".slb": "application/x-slb",
".slk": "drawing/x-slk",
".smil": "application/smil",
".snd": "audio/basic",
".sor": "text/plain",
".spl": "application/futuresplash",
".ssm": "application/streamingmedia",
".stl": "application/vnd.ms-pki.stl",
".sty": "application/x-sty",
".swf": "application/x-shockwave-flash",
".tg4": "application/x-tg4",
".tif": "image/tiff",
".tiff": "image/tiff",
".top": "drawing/x-top",
".tsd": "text/xml",
".uin": "application/x-icq",
".vcf": "text/x-vcard",
".vdx": "application/vnd.visio",
".vpg": "application/x-vpeg005",
".vsd": "application/x-vsd",
".vst": "application/vnd.visio",
".vsw": "application/vnd.visio",
".vtx": "application/vnd.visio",
".wav": "audio/wav",
".wb1": "application/x-wb1",
".wb3": "application/x-wb3",
".wiz": "application/msword",
".wk4": "application/x-wk4",
".wks": "application/x-wks",
".wma": "audio/x-ms-wma",
".wmf": "application/x-wmf",
".wmv": "video/x-ms-wmv",
".wmz": "application/x-ms-wmz",
".wpd": "application/x-wpd",
".wpl": "application/vnd.ms-wpl",
".wr1": "application/x-wr1",
".wrk": "application/x-wrk",
".ws2": "application/x-ws",
".wsdl": "text/xml",
".xdp": "application/vnd.adobe.xdp",
".xfd": "application/vnd.adobe.xfd",
".xhtml": "text/html",
".xls": "application/x-xls",
".xml": "text/xml",
".xq": "text/xml",
".xquery": "text/xml",
".xsl": "text/xml",
".xwd": "application/x-xwd",
".sis": "application/vnd.symbian.install",
".x_t": "application/x-x_t",
".apk": "application/vnd.android.package-archive",
"0.301": "application/x-301",
"0.906": "application/x-906",
".a11": "application/x-a11",
".ai": "application/postscript",
".aifc": "audio/aiff",
".anv": "application/x-anv",
".asf": "video/x-ms-asf",
".asx": "video/x-ms-asf",
".avi": "video/avi",
".biz": "text/xml",
".bot": "application/x-bot",
".c90": "application/x-c90",
".cat": "application/vnd.ms-pki.seccat",
".cdr": "application/x-cdr",
".cer": "application/x-x509-ca-cert",
".cgm": "application/x-cgm",
".class": "java/*",
".cmp": "application/x-cmp",
".cot": "application/x-cot",
".crt": "application/x-x509-ca-cert",
".css": "text/css",
".dbf": "application/x-dbf",
".dbx": "application/x-dbx",
".dcx": "application/x-dcx",
".dgn": "application/x-dgn",
".dll": "application/x-msdownload",
".dot": "application/msword",
".dtd": "text/xml",
".dwf": "application/x-dwf",
".dxb": "application/x-dxb",
".edn": "application/vnd.adobe.edn",
".eml": "message/rfc822",
".epi": "application/x-epi",
".eps": "application/postscript",
".exe": "application/x-msdownload",
".fdf": "application/vnd.fdf",
".fo": "text/xml",
".g4": "application/x-g4",
".tif": "image/tiff",
".gl2": "application/x-gl2",
".hgl": "application/x-hgl",
".hpg": "application/x-hpgl",
".hqx": "application/mac-binhex40",
".hta": "application/hta",
".htm": "text/html",
".htt": "text/webviewhtml",
".icb": "application/x-icb",
".ico": "application/x-ico",
".ig4": "application/x-g4",
".iii": "application/x-iphone",
".ins": "application/x-internet-signup",
".IVF": "video/x-ivf",
".jfif": "image/jpeg",
".jpe": "application/x-jpe",
".jpg": "image/jpeg",
".js": "application/x-javascript",
".la1": "audio/x-liquid-file",
".latex": "application/x-latex",
".lbm": "application/x-lbm",
".ls": "application/x-javascript",
".m1v": "video/x-mpeg",
".m3u": "audio/mpegurl",
".mac": "application/x-mac",
".math": "text/xml",
".mdb": "application/x-mdb",
".mht": "message/rfc822",
".mi": "application/x-mi",
".midi": "audio/mid",
".mml": "text/xml",
".mns": "audio/x-musicnet-stream",
".movie": "video/x-sgi-movie",
".mp2": "audio/mp2",
".mp3": "audio/mp3",
".mpa": "video/x-mpg",
".mpe": "video/x-mpeg",
".mpg": "video/mpg",
".mpp": "application/vnd.ms-project",
".mpt": "application/vnd.ms-project",
".mpv2": "video/mpeg",
".mpx": "application/vnd.ms-project",
".mxp": "application/x-mmxp",
".nrf": "application/x-nrf",
".odc": "text/x-ms-odc",
".p10": "application/pkcs10",
".p7b": "application/x-pkcs7-certificates",
".p7m": "application/pkcs7-mime",
".p7s": "application/pkcs7-signature",
".pci": "application/x-pci",
".pcx": "application/x-pcx",
".pdf": "application/pdf",
".pfx": "application/x-pkcs12",
".pic": "application/x-pic",
".pl": "application/x-perl",
".pls": "audio/scpls",
".png": "image/png",
".pot": "application/vnd.ms-powerpoint",
".ppm": "application/x-ppm",
".ppt": "application/vnd.ms-powerpoint",
".pr": "application/x-pr",
".prn": "application/x-prn",
".ps": "application/x-ps",
".ptn": "application/x-ptn",
".r3t": "text/vnd.rn-realtext3d",
".ram": "audio/x-pn-realaudio",
".rat": "application/rat-file",
".rec": "application/vnd.rn-recording",
".rgb": "application/x-rgb",
".rjt": "application/vnd.rn-realsystem-rjt",
".rle": "application/x-rle",
".rmf": "application/vnd.adobe.rmf",
".rmj": "application/vnd.rn-realsystem-rmj",
".rmp": "application/vnd.rn-rn_music_package",
".rmvb": "application/vnd.rn-realmedia-vbr",
".rnx": "application/vnd.rn-realplayer",
".rpm": "audio/x-pn-realaudio-plugin",
".rt": "text/vnd.rn-realtext",
".rtf": "application/x-rtf",
".sam": "application/x-sam",
".sdp": "application/sdp",
".sit": "application/x-stuffit",
".sld": "application/x-sld",
".smi": "application/smil",
".smk": "application/x-smk",
".sol": "text/plain",
".spc": "application/x-pkcs7-certificates",
".spp": "text/xml",
".sst": "application/vnd.ms-pki.certstore",
".stm": "text/html",
".svg": "text/xml",
".tdf": "application/x-tdf",
".tga": "application/x-tga",
".tif": "application/x-tif",
".tld": "text/xml",
".torrent": "application/x-bittorrent",
".txt": "text/plain",
".uls": "text/iuls",
".vda": "application/x-vda",
".vml": "text/xml",
".vsd": "application/vnd.visio",
".vss": "application/vnd.visio",
".vst": "application/x-vst",
".vsx": "application/vnd.visio",
".vxml": "text/xml",
".wax": "audio/x-ms-wax",
".wb2": "application/x-wb2",
".wbmp": "image/vnd.wap.wbmp",
".wk3": "application/x-wk3",
".wkq": "application/x-wkq",
".wm": "video/x-ms-wm",
".wmd": "application/x-ms-wmd",
".wml": "text/vnd.wap.wml",
".wmx": "video/x-ms-wmx",
".wp6": "application/x-wp6",
".wpg": "application/x-wpg",
".wq1": "application/x-wq1",
".wri": "application/x-wri",
".ws": "application/x-ws",
".wsc": "text/scriptlet",
".wvx": "video/x-ms-wvx",
".xdr": "text/xml",
".xfdf": "application/vnd.adobe.xfdf",
".xls": "application/vnd.ms-excel",
".xlw": "application/x-xlw",
".xpl": "audio/scpls",
".xql": "text/xml",
".xsd": "text/xml",
".xslt": "text/xml",
".x_b": "application/x-x_b",
".sisx": "application/vnd.symbian.install",
".ipa": "application/vnd.iphone",
".xap": "application/x-silverlight-app",
".zip": "application/x-zip-compressed",
}
function FormData() {
let fileManager = uni.getFileSystemManager();
let data = {};
let files = [];
this.append = (name, value) => {
data[name] = value;
return true;
}
this.appendFile = (name, path) => {
let buffer = fileManager.readFileSync(path);
if (Object.prototype.toString.call(buffer).indexOf("ArrayBuffer") < 0) {
return false;
}
files.push({
name: name,
buffer: buffer,
fileName: getFileNameFromPath(path)
});
return true;
}
this.batchAppendFile = (fileArr, keyName = "name", pathName = "path") => {
// [{name: "file", path: filePath }]
for (let i = 0; i < fileArr.length; i++) {
let name = fileArr[i][keyName],
path = fileArr[i][pathName];
let buffer = fileManager.readFileSync(path);
if (Object.prototype.toString.call(buffer).indexOf("ArrayBuffer") < 0) {
return false;
}
files.push({
name: name,
buffer: buffer,
fileName: getFileNameFromPath(path)
});
}
return true;
}
this.getData = () => convert(data, files)
}
function getFileNameFromPath(path) {
let idx = path.lastIndexOf("/");
return path.substr(idx + 1);
}
function convert(data, files) {
let boundaryKey = 'wxmpFormBoundary' + randString(); // 数据分割符,一般是随机的字符串
let boundary = '--' + boundaryKey;
let endBoundary = boundary + '--';
let postArray = [];
//拼接参数
if (data && Object.prototype.toString.call(data) == "[object Object]") {
for (let key in data) {
postArray = postArray.concat(formDataArray(boundary, key, data[key]));
}
}
//拼接文件
if (files && Object.prototype.toString.call(files) == "[object Array]") {
for (let i in files) {
let file = files[i];
postArray = postArray.concat(formDataArray(boundary, file.name, file.buffer, file.fileName));
}
}
//结尾
let endBoundaryArray = [];
for (var i = 0; i < endBoundary.length; i++) { // 最后取出结束boundary的charCode
endBoundaryArray.push(...endBoundary.utf8CodeAt(i));
}
postArray = postArray.concat(endBoundaryArray);
return {
contentType: 'multipart/form-data; boundary=' + boundaryKey,
buffer: new Uint8Array(postArray).buffer
}
}
function randString() {
let res = "";
for (let i = 0; i < 17; i++) {
let n = parseInt(Math.random() * 62);
if (n <= 9) {
res += n;
} else if (n <= 35) {
res += String.fromCharCode(n + 55);
} else {
res += String.fromCharCode(n + 61);
}
}
return res;
}
function formDataArray(boundary, name, value, fileName) {
let dataString = '';
let isFile = !!fileName;
dataString += boundary + '\r\n';
dataString += 'Content-Disposition: form-data; name="' + name + '"';
if (isFile) {
dataString += '; filename="' + fileName + '"' + '\r\n';
dataString += 'Content-Type: ' + getFileMime(fileName) + '\r\n\r\n';
} else {
dataString += '\r\n\r\n';
dataString += value;
}
var dataArray = [];
for (var i = 0; i < dataString.length; i++) { // 取出文本的charCode(10进制)
dataArray.push(...dataString.utf8CodeAt(i));
}
if (isFile) {
let fileArray = new Uint8Array(value);
dataArray = dataArray.concat(Array.prototype.slice.call(fileArray));
}
dataArray.push(..."\r".utf8CodeAt());
dataArray.push(..."\n".utf8CodeAt());
return dataArray;
}
function getFileMime(fileName) {
let idx = fileName.lastIndexOf(".");
let mime = mimeMap[fileName.substr(idx)];
return mime ? mime : "application/octet-stream"
}
String.prototype.utf8CodeAt = function(i) {
var str = this;
var out = [],
p = 0;
var c = str.charCodeAt(i);
if (c < 128) {
out[p++] = c;
} else if (c < 2048) {
out[p++] = (c >> 6) | 192;
out[p++] = (c & 63) | 128;
} else if (
((c & 0xFC00) == 0xD800) && (i + 1) < str.length &&
((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) {
// Surrogate Pair
c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);
out[p++] = (c >> 18) | 240;
out[p++] = ((c >> 12) & 63) | 128;
out[p++] = ((c >> 6) & 63) | 128;
out[p++] = (c & 63) | 128;
} else {
out[p++] = (c >> 12) | 224;
out[p++] = ((c >> 6) & 63) | 128;
out[p++] = (c & 63) | 128;
}
return out;
};
export default FormData;
当移动端要用到的时候拿过来 然后new FormData()即可
当然h5 是可以使用webApi的直接new 就行
3.2、微信小程序域名白名单配置指南
1. 登录微信公众平台
首先,访问微信公众平台官网 https://mp.weixin.qq.com,使用小程序管理员账号登录
2. 进入小程序后台
登录后,选择您要配置的小程序项目。
3. 配置服务器域名
- 在左侧菜单栏中,点击【开发】→【开发管理】→【开发设置】
- 向下滚动找到【服务器域名】部分
4. 添加域名白名单
在服务器域名部分,您会看到以下几种类型的域名配置:
- request合法域名:用于wx.request等网络请求接口
- socket合法域名:用于wx.connectSocket等WebSocket接口
- uploadFile合法域名:用于wx.uploadFile上传文件接口
- downloadFile合法域名:用于wx.downloadFile下载文件接口
根据您的需求,在相应的输入框中添加您的API域名。
5. 域名要求
添加域名时需要注意以下几点:
- 域名必须经过ICP备案
- 域名必须是HTTPS(SSL证书有效)
- 不支持IP地址(包括本地IP和公网IP)
- 不支持localhost
- 域名不能使用端口号(如www.example.com:8080)
- 每个类型最多可配置20个域名
6. 保存配置
添加完域名后,点击【保存】按钮。
7. 验证域名所有权(如需要)
首次添加域名时,可能需要验证域名所有权:
- 下载校验文件
2. 将校验文件上传到域名根目录
- 确保可以通过 https://您的域名/.well-known/wechat-verification-file.txt 访问该文件
- 点击【确认】完成验证
8. 开发环境设置
在开发阶段,可以临时关闭域名校验:
- 在开发工具中,点击右上角【详情】
- 勾选【不校验合法域名...】选项
注意:此设置仅在开发环境有效,上线版本必须配置合法域名。
9. 常见问题
- 配置后不生效:域名配置有一定的生效延迟,一般在10分钟内
- 测试号限制:测试号最多只能配置5个域名
- 子域名问题:需要分别添加子域名,如添加了api.example.com不代表自动支持example.com
10. 特殊情况
如果您使用的是云开发或第三方平台,可能有特殊的域名配置要求,请参考相关文档。
三、权限处理
要注意权限处理 比如
用户授权
位置、相机、相册等需要用户授权 使用uni.authorize()或相关API获取权限
正常来说相机 和相册的拉起并没有那么复杂并且 有https的情况下正常使用uni.choose的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;
....
};
2、麦克风权限 相关拾音的比较麻烦一点
const initAuthorize = (callback, isAction = true) => {
// 向用户发起授权请求
// #ifdef MP-WEIXIN
uni.authorize({
scope: 'scope.record', // 获取录音功能,也就是麦克风权限
success: (res) => {
// 用户授权使用麦克风权限调用语音搜索事件函数
isAuth.value = true
callback && callback()
},
// 用户没有授权使用麦克风权限执行以下代码
fail(res) {
isAuth.value = false
// 显示模态弹窗提示用户没有开启麦克风权限
uni.showModal({
content: '检测到您未开启麦克风权限,请保持麦克风权限为开启状态',
confirmText: '去开启',
confirmColor: '#01B3FD',
cancelText: '取消',
showCancel: true,
success: (res) => {
if (res.confirm) {
// 调起客户端小程序设置界面,返回用户设置的操作结果
uni.openSetting({
success: (res) => {
if (res.authSetting['scope.record'] == true) {
uni.showToast({
title: '授权成功',
icon: 'none'
});
isAuth.value = true;
isAuth.value && callback &&
callback()
} else if (res.authSetting['scope.record'] ==
false) {
uni.showToast({
title: '授权失败',
icon: 'none'
});
isAuth.value = false
}
}
})
}
}
})
},
complete() {
console.log("authorize['scope.record'] complete")
}
})
}
4、手机号 微信授权登录
<button class="wx-login-btn"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
plain="true">
手机号一键登录
</button>
// 微信获取手机号码登录
// 3、调用getPhoneNumber
const getPhoneNumber = async (e) => {
// console.log(agree.value)
if (agree.value instanceof Array && agree.value.length === 0) {
uni.showToast({
title: '请先勾选用户协议',
icon: 'none'
})
return false;
}
// console.log('获取手机号码======code:', e, e.detail.code)
let phoneCode = e.detail.code;
if (agree.value.length === 0) {
uni.showToast({
title: '请先勾选用户协议',
icon: 'none'
})
return false;
}
if (phoneCode) {
console.log("有")
uni.login({
provider: 'weixin', //使用微信登录
success: async function(res) {
console.log("获取微信id===>", res.code);
let wechatCode = res.code
let loginRes = await login({
loginType: 2,
phoneCode: phoneCode,
wechatCode: wechatCode
})
//....
}
});
} else {
console.log("没有")
loginPop.value.close()
}
// 注意:如果获取不到code 确认下开发者工具调试基础库版本是否为 2.21.2 及以上
}
四、打包
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:配置文件