您现在的位置是:网站首页> 小程序设计
uniapp实战总结
- 小程序设计
- 2024-08-25
- 1400人已阅读
uniapp实战总结(分包完之后路径发生了改变,切记!!!切记!!!切记!!!)
首先必须是企业小程序,才能使用webview,个人的不支持使用
在uniapp设计的APP中引入axios,支持cookie(真机亲测可行)
uni-app webrtc 实现H5音视频通讯(点击查看原文)
uniapp 中 ArrayBuffer 和 json 格式如何互转
生成签名
keytool -genkey -alias hbuilder -keyalg RSA -keysize 2048 -validity 36500 -keystore hbuilder.keystore
hbuilder是证书别名,可修改为自己想设置的字符,建议使用英文字母和数字
hbuilder.keystore是证书文件名称,可修改为自己想设置的文件名称,也可以指定完整文件路径
36500是证书的有效期,表示100年有效期,单位天,建议时间设置长一点,避免证书过期
回车后会提示:
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: //输入密码,回车
HBuilder 离线打包修改appid(修改包名)
1、修改manifest.json中的id
2、修改包名
文字省略
u-line-1,u-line-2,u-line-3,u-line-4,u-line-5五个类名在文字超出内容盒子时,分别只显示一行、两行、三行、四行、五行+省略号。
边框
u-border表示给元素添加四周的边框,u-border-top为上边框,u-border-right为右边框, u-border-bottom为下边框,u-border-left为左边框。
地图上绘制
<map style="width: 100%; height: 90vh;" :layer-style='5' :style="{height:mapheight}" :show-location='true' :latitude="latitude" :longitude="longitude" :markers="marker" :scale="scale" @markertap="markertap" @callouttap='callouttap'>
<cover-view class="cover-view" :style='{bottom:coverbottom}' @click="onControltap">
<cover-image class="cover-image" @click="play" src="@/static/ditudingwei.png"></cover-image>
<cover-view>
定位
</cover-view>
</cover-view>
</map>
<style scoped>
.map-container {
position: relative;
overflow: hidden;
.cover-view {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 80rpx;
height: 80rpx;
color: #484848;
background-color: #fff;
background-size: 120rpx 120rpx;
background-position: center center;
position: absolute;
bottom: 100rpx;
right: 32rpx;
}
.cover-image{
display: inline-block;
width: 30rpx;
height: 30rpx;
}
}
</style>
webview交互页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title>网络网页</title>
<style type="text/css">
.btn {
display: block;
margin: 20px auto;
padding: 5px;
background-color: #007aff;
border: 0;
color: #ffffff;
height: 40px;
width: 200px;
}
.btn-red {
background-color: #dd524d;
}
.btn-yellow {
background-color: #f0ad4e;
}
.desc {
padding: 10px;
color: #999999;
}
.post-message-section {
visibility: hidden;
}
</style>
</head>
<body>
<p class="desc">web-view 组件加载网络 html 示例。点击下列按钮,跳转至其它页面。</p>
<div class="btn-list">
<button class="btn" type="button" data-action="navigateTo">navigateTo</button>
<button class="btn" type="button" data-action="redirectTo">redirectTo</button>
<button class="btn" type="button" data-action="navigateBack">navigateBack</button>
<button class="btn" type="button" data-action="reLaunch">reLaunch</button>
<button class="btn" type="button" data-action="switchTab">switchTab</button>
</div>
<div class="post-message-section">
<p class="desc">网页向应用发送消息,注意:小程序端应用会在此页面后退时接收到消息。</p>
<div class="btn-list">
<button class="btn btn-red" type="button" id="postMessage">postMessage</button>
</div>
</div>
<script type="text/javascript">
var userAgent = navigator.userAgent;
if (userAgent.indexOf('AlipayClient') > -1) {
// 支付宝小程序的 JS-SDK 防止 404 需要动态加载,如果不需要兼容支付宝小程序,则无需引用此 JS 文件。
document.writeln('<script src="https://appx/web-view.min.js"' + '>' + '<' + '/' + 'script>');
} else if (/QQ/i.test(userAgent) && /miniProgram/i.test(userAgent)) {
// QQ 小程序
document.write('<script type="text/javascript" src="https://qqq.gtimg.cn/miniprogram/webview_jssdk/qqjssdk-1.0.0.js"><\/script>');
} else if (/miniProgram/i.test(userAgent) && /micromessenger/i.test(userAgent)) {
// 微信小程序 JS-SDK 如果不需要兼容微信小程序,则无需引用此 JS 文件。
document.write('<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"><\/script>');
} else if (/toutiaomicroapp/i.test(userAgent)) {
// 头条小程序 JS-SDK 如果不需要兼容头条小程序,则无需引用此 JS 文件。
document.write('<script type="text/javascript" src="https://s3.pstatp.com/toutiao/tmajssdk/jssdk-1.0.1.js"><\/script>');
} else if (/swan/i.test(userAgent)) {
// 百度小程序 JS-SDK 如果不需要兼容百度小程序,则无需引用此 JS 文件。
document.write('<script type="text/javascript" src="https://b.bdstatic.com/searchbox/icms/searchbox/js/swan-2.0.18.js"><\/script>');
} else if (/quickapp/i.test(userAgent)) {
// quickapp
document.write('<script type="text/javascript" src="https://quickapp/jssdk.webview.min.js"><\/script>');
}
if (!/toutiaomicroapp/i.test(userAgent)) {
document.querySelector('.post-message-section').style.visibility = 'visible';
}
</script>
<!-- uni 的 SDK -->
<script type="text/javascript" src="https://unpkg.com/@dcloudio/[email protected]/index.js"></script>
<script type="text/javascript">
document.addEventListener('UniAppJSBridgeReady', function() {
document.querySelector('.btn-list').addEventListener('click', function(evt) {
var target = evt.target;
if (target.tagName === 'BUTTON') {
var action = target.getAttribute('data-action');
switch (action) {
case 'switchTab':
uni.switchTab({
url: '/pages/tabBar/API/API'
});
break;
case 'reLaunch':
uni.reLaunch({
url: '/pages/tabBar/component/component'
});
break;
case 'navigateBack':
uni.navigateBack({
delta: 1
});
break;
default:
uni[action]({
url: '/pages/component/button/button'
});
break;
}
}
});
document.getElementById('postMessage').addEventListener('click', function() {
uni.postMessage({
data: {
action: 'message'
}
});
});
});
</script>
</body>
</html>
本地页面
┌─components
├─hybrid
│ └─html
│ ├─css
│ │ └─test.css
│ ├─img
│ │ └─icon.png
│ ├─js
│ │ └─test.js
│ └─local.html
├─pages
│ └─index
│ └─index.vue
├─static
├─main.js
├─App.vue
├─manifest.json
└─pages.json
<template>
<view>
<web-view src="/hybrid/html/local.html"></web-view>
</view>
</template>
在uniapp设计的APP中引入axios,支持cookie(真机亲测可行)
在uniapp中,我们可以通过原生的uni.request(OBJECT)方法发起网络请求,但是这个请求方式不管是在h5、app、还是微信小程序,都是不支持cookie的。这里就想到在vue项目中经常用到的请求方式axios,是支持cookie的,那我们是不是可以把axios也引入到uniapp中使用呢?有了这个想法,我们就开始动手做起来吧!
首先就是正常的安装和引用axios:
1.在你的uniapp项目的根目录下,运行命令行语句:
npm install axios
2.在main.js中引入axios
import Vue from 'vue'
import App from './App'
import axios from 'axios'
// create an axios instance
const service = axios.create({
baseURL: 'http://192.168.0.105:8090', // url = base url + request url
withCredentials: true, // send cookies when cross-domain requests
// timeout: 5000, // request timeout
crossDomain: true
})
Vue.prototype.$axios = service
3.创建axios.js文件,导出axios方法
import Vue from 'vue'
export default Vue.prototype.$axios
4.使用axios进行方法请求
到这一步,我们就可以用axios进行请求了。但是问题也来了,这个请求方式在h5中可以执行,但是在app和微信小程序运行的就会出现下面这个错误:
adapter是axios的适配器,可在adapter中设置属于自己的请求方法,这里报错大概是axios默认的适配器并没有被uniapp识别到,所以我们在这里就自己定义个适配器。这里就是基于Promise封装了uniapp的request方法,代码如下:
axios.defaults.adapter = function(config) {
return new Promise((resolve, reject) => {
console.log(config)
var settle = require('axios/lib/core/settle');
var buildURL = require('axios/lib/helpers/buildURL');
uni.request({
method: config.method.toUpperCase(),
url: config.baseURL + buildURL(config.url, config.params, config.paramsSerializer),
header: config.headers,
data: config.data,
dataType: config.dataType,
responseType: config.responseType,
sslVerify: config.sslVerify,
complete: function complete(response) {
response = {
data: response.data,
status: response.statusCode,
errMsg: response.errMsg,
header: response.header,
config: config
};
settle(resolve, reject, response);
}
})
})
}
在main.js中放入这段自定义适配器的代码,就可以实现uniapp的app和小程序开发中能使用axios进行跨域网络请求,并支持携带cookie。
uniapp使用MQTT
在uniapp项目根目录下分别运行安装mqtt和uuid的命令行,因为后面会用uuid生成mqtt的clientId,所以这边就一起安装了。
1.安装mqtt和uuid
npm install [email protected]
npm install uuid
①我这里和uniapp提供的插件安装的mqtt版本一样,我也尝试装了最新的版本,会报错,emmmmm...........
②如果没有pakage.json,安装是会提示报错,但是不影响安装使用。如果想方便一点,下次拉代码直接安装的话,可以自己在项目根目录下加一个pakage.json文件,添加如下内容:
{
"name": "",
"version": "1.0.0",
"description": "",
"author": "",
"license": "MIT",
"dependencies": {
"mqtt": "^3.0.0",
"uuid": "^8.3.0"
},
"devDependencies": {},
"scripts": {}
}
2.页面引入mqtt并调用
①mqtt连接配置,放在/utils/mqtt.js里面,全局可用。
export const MQTT_IP = '192.168.9.128:8083/mqtt'//mqtt地址端口
const MQTT_USERNAME = 'public'//mqtt用户名
const MQTT_PASSWORD = 'public'//密码
export const MQTT_OPTIONS = {
connectTimeout: 5000,
clientId: '',
username: MQTT_USERNAME,
password: MQTT_PASSWORD,
clean: false
}
②vue页面引用mqtt
mqtt里面的clientId用uuid生成唯一标识码,防止不同页面订阅不同主题时数据出现粘连
<script>
import { v4 } from 'uuid';
import {
MQTT_IP,
MQTT_OPTIONS
} from '@/utils/mqtt.js';
var mqtt = require('mqtt/dist/mqtt.js')
var client
export default {
data() {
return {
topic: '' //要订阅的主题
}
},
mounted() {this.connect() //连接
},
methods: {
connect() {
MQTT_OPTIONS.clientId = v4()
var that = this
// #ifdef H5
client = mqtt.connect('ws://' + MQTT_IP, MQTT_OPTIONS)
// #endif
// #ifdef MP-WEIXIN||APP-PLUS
client = mqtt.connect('wx://' + MQTT_IP, MQTT_OPTIONS)
// #endif
client.on('connect', function() {
console.log('连接成功')
client.subscribe(that.topic, function(err) {
if (!err) {
console.log('订阅成功')
}
})
}).on('reconnect', function(error) {
console.log('正在重连...', that.topic)
}).on('error', function(error) {
console.log('连接失败...', error)
}).on('end', function() {
console.log('连接断开')
}).on('message', function(topic, message) {
console.log('接收推送信息:', message.toString())
})
}
}
}
</script>
以上就是uniapp中使用mqtt的方法分享
页面上拉刷新配置
第一步有这个需求时 在pages.json配置开启下拉刷新
例如:
{
"path":"pages/components/hold/hold",
"style": {
"navigationStyle": "custom",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true, //开始下拉
}
},
第二步
methods: {
//上拉
onPullDownRefresh() {
setTimeout( ()=>{
this.init( ()=>{
uni.stopPullDownRefresh();
});
uni.showToast({
"title":'上拉刷新成功',
})
},1000)
},
//下拉
onReachBottom() {
uni.showToast({
"title":'已全部加载',
})
},
}
uniapp打包为桌面应用
1.在控制台安装electron
cnpm install electron -g
2、在控制台安装-packager
cnpm install electron-packager -g
uniapp的manifest.json修改
H5设置
运行的基础路径修改为:./ 不然打包出来会出现白屏,读取不到,因为打包出来的h5默认加载地址为/static/
去掉启用https协议: 不然会出现网络无法加载,去掉https不影响你请求后端的https协议
H5打包
将打包后的H5在用带三方打包工具生成桌面app
比如用electron
H5文件夹下新建package.json和main.js
新建package.json
{ "name" : "exam考试系统", "version" : "0.1.0", "main" : "main.js"}
新建main.js
const {app, BrowserWindow} = require('electron')
const path = require('path')
const url = require('url')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
function createWindow () {
// Create the browser window.
win = new BrowserWindow({width: 400, height: 700})
// and load the index.html of the app.
win.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
// Open the DevTools.
// win.webContents.openDevTools()
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
如果你的网页首页的文件名不是 “index.html”,那么请在 main.js 中将其中的 'index.html' 修改为你的网页首页名
打包
建议使用cmd,本人使用powershell和git hash有踩坑
cmd命令行进入H5目录,输入打包命令
electron-packager . helloWorld --platform=win32 --arch=x64 --icon=computer.ico --out=./out --asar --app-version=1.0.0 --overwrite --ignore=node_modules --electron-version 8.2.1
参数说明:
HelloWorld :你将要生成的exe文件的名称
–platform=win32:确定了你要构建哪个平台的应用,可取的值有 darwin, linux, mas, win32
–arch=x64:决定了使用 x86 还是 x64 还是两个架构都用
–icon=自定义.ico:自定义设置应用图标
–out=./out:指定打包文件输出的文件夹位置,当前指定的为项目目录下的out文件夹
–asar:该参数可以不加,如果加上,打包之后应用的源码会以.asar格式存在
–app-version=1.0.0:生成应用的版本号
–overwrite:覆盖原有的build,让新生成的包覆盖原来的包
–ignore=node_modules:如果加上该参数,项目里node_modules模块不会被打包进去
–electron-version 8.2.1:指定当前要构建的electron的电子版本(不带"v"),需要和当前的版本一致,具体可以在 package.json文件中查看,可以不加该参数,如果不一致,会自动下载。
【打包慢,无反应】
(1)更换为阿里镜像源 命令行下输入
npm config set ELECTRON_MIRROR http://npm.taobao.org/mirrors/electron/
(2)修改环境变量
我的电脑->右键->属性->高级系统设置->高级->环境变量->新建
更新内容后再次使用HbuilderX生成h5前记得备份
package.json
main.js
打包成功后,根目录会生成一个新的文件夹,点进去,找到 exe 文件,双击就可以看到网页变成了一个桌面应用啦!
小程序支付功能
uniapp 直播拉流 播放m3u8 视频
在百度中找啦n多个方法 没有解决啦 巨气人
发现hls.js hls.js不需要任何播放器,它可以直接在标准HTML 元素上运行。
安装第三方库
npm install hls.js -S
在uniapp页面显示
<template>
<div class="video_con">
<video controls class="video" ref="video"></video>
</div>
</template>
<script>
let Hls = require('hls.js');
export default {
data() {
return {
hls: null
}
},
mounted() {
this.getStream('url')
},
methods: {
getStream(source) {
if (Hls.isSupported()) {
this.hls = new Hls();
this.hls.loadSource(source);
this.hls.attachMedia(this.$refs.video);
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
console.log("加载成功");
this.$refs.video.play();
});
this.hls.on(Hls.Events.ERROR, (event, data) => {
// console.log(event, data);
// 监听出错事件
console.log("加载失败");
});
} else if (this.$refs.video.canPlayType('application/vnd.apple.mpegurl')) {
this.$refs.video.src = 'https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8';
this.$refs.video.addEventListener('loadedmetadata',function() {
this.$refs.video.play();
});
}
},
// 停止流
videoPause() {
if (this.hls) {
this.$refs.video.pause();
this.hls.destroy();
this.hls = null;
}
}
}
}
</script>
如果想要应用在自己的页面中 直接c v 就ok啦
uni-app webrtc 实现H5音视频通讯
1. 写在前面
之前本人一直没有做过webrtc相关的开发(进行实时语音对话或视频对话的),我上家公司的老板突然找到我,让我帮他做一个webrtc的模块功能。通过uni-app 去开发,然后打包到H5网页上进行音视频沟通。我主要是没有接触过,也不知道怎么去做,只是会uni-app,但是去对接webrtc 拿到手一脸雾水。不知道从何开始。后面各种百度,各种查资料,算是把这个功能搞出来了,现在想起来还是挺心酸的。
uni-app websocket 开发参考 https://uniapp.dcloud.io/api/request/websocket.html
webrtc 开发参考 https://webrtc.github.io/samples/
2. 项目需求 (安全帽视频对接)
安全帽是一个特制的帽子,不同与普通的安全帽,而是一个有电源开关,有摄像头,有开灯光的帽子。
通俗点就是,安全帽那边是一个工地的工作人员(A端),带上帽子进行作业。遇到困难,需要办公室高级技术人员(B端)去指挥工人作业操作,安全帽A端是无法看到B端,但是B端可以通过在H5网页上,然后进行观A端看那边的作业情况,进行指挥。
2.1 完成效果
3. 开始搞,uni-app 开发H5视频对接
3.1 html代码
就是声明一个video标签,进行视频播放使用,(关键的,那几个按钮的不重要这里不写了)
<template>
<view class="container">
<div class="video-cont">
<video id="remoteVideo" :muted="muted" autoplay></video>
</div>
</view>
</template>
3.2 js 代码(核心步骤)
3.2.1 根据接口获取安全帽在线的房间号,点击在线的安全帽列表,进入视频页面观看
data 数据
return {
hatid: '', //房间号
ws: null,//ws
socket: null,//socket
state: 'init',//状态
pc: null,
localStream: null,
socketOpen: false,
muted: false, //是否静音
};
进入页面获取列表传的 hatid ,调用 initWebSocket 方法
onLoad(option) {
if (option.hat_id) {
this.hatid = option.hat_id
}
},
onReady() {
this.initWebSocket(this.hatid); //连接WebSocket
},
3.2.2 进入房间后,首先 uni.connectSocket 创建初始化websocket连接
this.ws = uni.connectSocket({
url: "wss://rtc.xxxxxxx.cn/ws",//你自己的地址
success: (res) => {
console.log("WebSocket服务连接成功!");
},
fail: (err) => {
uni.showToast({
title: JSON.stringify(err),
icon: 'error'
});
}
});
3.2.3 uni.onSocketOpen 打开连接,向服务端发送进入房间信息;并且创建心跳,每隔10s发送心跳信息。用于判断连接状态,如果断开,需要重新连接。
// 2. 连接打开
uni.onSocketOpen((res) => {
this.socketOpen = true
// 打开后发送一条消息
uni.sendSocketMessage({
data: `{"isHat":"N" ,"type":"on_line" ,"hatId":${this.hatid} }`
});
// 10s 发送一次心跳
this.heartbeatInterval = setInterval(() => {
// console.log("轮询监听WebSocket状态:" + this.ws.readyState)
// CONNECTING 0 OPEN 1 打卡状态 CLOSING 2 CLOSED 3 断开状态
if (this.ws.readyState === this.ws.OPEN) {
// 打开状态
uni.sendSocketMessage({
data: "keep alive admin:" + 'xiehao' + " connect:" + this.hatid,
});
} else if (this.ws.readyState === this.ws.CLOSED) {
// 判断如果断开,需要重新链接
this.initWebSocket(this.hatid)
} else if (this.ws.readyState === this.ws.CLOSING || this.ws.readyState === this.ws
.CONNECTING) {
//不用管
}
}, 10000)
});
可以看到,我们已经创建了连接,并且在发送心跳信息,服务的响应消息为的 allow
3.2.4 uni.onSocketMessage 进行服务端响应消息监听,
这里判断如果返回消息为full 则是房间已满,无法进行查询通话
如果返回 allow 则没有人进入房间,允许进入房间进行通话,然后进入方法 connSignalServer 连接音视频
uni.onSocketMessage((res) => {
var msg = res.data;
if (msg.indexOf("full") !== -1) {
uni.showToast({
title: '当前安全帽有人在查看,您暂时无法查看!',
icon: 'error'
});
this.state = 'full';
} else if (msg.indexOf("allow") !== -1) {
console.log("准备连接音视频。。。。。。")
this.connSignalServer(); //连接音视频
}
});
3.2.5 connSignalServer 进行连接音视频
navigator.mediaDevices 进行媒体兼容判断,如果浏览器支持播放,则进入connFun 方法
这里涉及到一个开发问题,则是在本地开发环境,浏览器访问需要使用https或者localhost进行访问,不能使用http进行访问,否则会走不下去,报错进入handleError 方法。
connSignalServer() {
// 开启本地视频
if (!navigator.mediaDevices ||
!navigator.mediaDevices.getUserMedia) {
alert("getUserMedia is not supported!")
return;
} else {
//1 ===============配置音视频参数===============
let constraints = {
video: false, //先设置为false进行调试
audio: true
}
navigator.mediaDevices.getUserMedia(constraints)
.then(this.getMediaStream)
.catch(this.handleError)
}
},
getMediaStream(stream) {
this.localStream = stream;
//这个函数的调用时机特别重要 一定要在getMediaStream之后再调用,否则会出现绑定失败的情况
this.connFun();
},
handleError(err) {
if (err) console.error("getUserMedia error:", err);
}
3.2.6 connFun 进行监听服务端返回的值,然后进行一些逻辑操作。
1 创建 socket 连接,emit 发送 join 进入房间 ,服务的正常会返回 joined 和 otherjoin (这个是根据前端和后端协商的,并不是固定的,只是我这里是这个。)
2 监听返回 joined 进入 createPeerConnection 方法
3 监听返回 otherjoin 进入 call 进行媒体协商
import io from './js/socket.io.js'
connFun() {
this.socket = io('https://rtc.xxxxxxx.cn/');
this.socket.on('joined', (roomid, id) => {
this.state = 'joined';
this.createPeerConnection()
});
this.socket.on('otherjoin', (roomid, id) => {
this.state = 'joined_conn';
//媒体协商
this.call();
});
this.socket.on('message', (roomid, id, data) => {
//媒体协商
if (data) {
if (data.type === 'offer') {
this.pc.setRemoteDescription(new RTCSessionDescription(data));
this.pc.createAnswer()
.then(this.getAnswer)
.catch(this.handleAnswerError);
} else if (data.type === 'answer') {
this.pc.setRemoteDescription(new RTCSessionDescription(data));
} else if (data.type === 'candidate') {
var candidate = new RTCIceCandidate({
sdpMLineIndex: data.label,
candidate: data.candidate
});
} else {
console.error('the message is invalid!', data)
}
}
});
if (this.socket.emit()) {
this.socket.emit('join', this.hatid);
}
return;
},
getAnswer(desc) {
this.pc.setLocalDescription(desc);
this.socket.emit('message', this.hatid, desc);
},
handleAnswerError(err) {
console.error('Failed to get Answer!', JSON.stringify(err));
},
3.2.7 createPeerConnection 创建本地流媒体链接
createPeerConnection() {
if (!this.pc) {
this.pc = new RTCPeerConnection({
'iceServers': [{
'urls': 'turn:175.178.21.191:xxxx',
'credential': 'xxxxxxxx',
'username': 'xxxx'
}],
});
this.pc.onicecandidate = (e) => {
if (e.candidate) {
this.socket.emit('message', this.hatid, {
type: 'candidate',
label: e.candidate.sdpMLineIndex,
id: e.candidate.sdpMid,
candidate: e.candidate.candidate
});
}
}
this.pc.ontrack = (e) => {
if (e.streams.length > 0) {
let videoElement = document.getElementsByTagName('video')[0]
videoElement.srcObject = e.streams[0];
}
}
}
if (this.pc === null || this.pc === undefined) {
console.log('pc is null or undefined!');
return;
}
if (this.localStream === null || this.localStream === undefined) {
console.log('localStream is null or undefined!');
// return;
}
if (this.localStream) {
this.localStream.getTracks().forEach((track) => {
this.pc.addTrack(track, this.localStream);
});
}
},
3.2.8 call 创建createOffer ,设置sdp,发送 message消息,发送sdp
在3.2.6 中 的方法,已经监听了服务的返回 message 消息
call() {
if (this.state === 'joined_conn') {
if (this.pc) {
var options = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
}
this.pc.createOffer(options)
.then(this.getOffer)
.catch(this.handleOfferError);
}
}
},
getOffer(desc) {
this.pc.setLocalDescription(desc);
if (this.socket) {
this.socket.emit('message', this.hatid, desc);
}
},
handleOfferError(err) {
console.error('Failed to get Offer!', JSON.stringify(err));
},
3.2.10 设置拉流到video中
这里使用原生的 document.getElementsByTagName('video')[0] 去获取,不使用refs,使用refs会报错
这里使用srcObject 不能使用src去设置
this.pc.ontrack = (e) => {
if(e.streams.length > 0) {
let videoElement = document.getElementsByTagName('video')[0]
videoElement.srcObject = e.streams[0];
}
}
4. webrtc 媒体协商过程解释
媒体协商是为了保证交互双方通过交换信息来保证交互的正常进行,比如A用的是H264编码,通过协商告知B,B来判断自己是否可以进行相应的数据解析来确定是否可以进行交互通信。WebRTC默认情况下使用的V8引擎。
4.1 媒体协商流程
首先发起端要创建一个offer,并调用setLocalDescription设置本地的SDP
然后通过信令服务器将含有SDP的offer设置给对端
对端拿到此offer以后调用setRemoteDescription将此SDP信息保存
对端创建一个answer,并调用setLocalDescription设置本地的SDP
通过信令服务器将含有SDP的answer发送给发起端
发起端调用setRemoteDescription将此SDP信息保存
4.2 媒体协商方法
createOffer
createAnswer
setLocalDescription
setRemoteDescription
NFC开发疑问
微信小程序接入NFC,使用HCE模拟主机卡完成NFC刷卡发送消息
微信小程序 NFC 踩坑记录,点击查看NFC API,点击查看NFC文档
需求
读取URL
写入URL
操作流程
一、读取
1. 获取NFC适配器实例
this.NFCAdapter = wx.getNFCAdapter()
2. 开始监听贴卡
this.NFCAdapter.startDiscovery()
3. 册贴卡监听回调
this.NFCAdapter.onDiscovered(function callback)
4. 注销NFC适配器实例
// 取消监听 NFC Tag
this.NFCAdapter.offDiscovered(function callback)
// 停止监听贴卡
this.NFCAdapter.stopDiscovery()
// 重置 NFC 实例
this.NFCAdapter = null
二、写入
1. 获取NFC适配器实例
this.NFCAdapter = wx.getNFCAdapter()
2. 开始监听贴卡
this.NFCAdapter.startDiscovery()
3. 开始监听 NFC 标签
this.NFCAdapter.onDiscovered(function callback)
4. 连接设备 NFC
const NFCTab = this.NFCAdapter.getNdef()
NFCTab.connect({
success: () => {
wx.showToast({ title: '连接设备成功' })
},
fail: error => {
wx.showToast({
title: '连接设备失败',
icon: 'error'
})
this.NFCAdapter.offDiscovered(function callback)
}
})
5. 写入数据(此处以写入uri为例,其它类型可查询官方文档)
NFCTab.writeNdefMessage({
uris: ['https://www.baidu.com'],
success: () => {
console.log('数据写入成功')
},
fail: () => {
console.log('数据写入失败')
},
complete: res => {
this.closeConnect(NFCTab)
}
})
6. 读写完毕,断开连接
function closeConnect(NFCTab) {
NFCTab.close({
complete: res => {
console.log('清除标签连接:res', res)
this.NFCAdapter.offDiscovered(function callback)
}
})
}
7. 注销NFC适配器实例
this.NFCAdapter.offDiscovered(function callback)
this.NFCAdapter.stopDiscovery()
this.NFCAdapter = null
数据转换
1. 字节对象转字符串
export const byteToString = function (arr) {
if (typeof arr === 'string') {
return arr
}
var str = '',
_arr = arr
for (var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/)
if (v && one.length == 8) {
var bytesLength = v[0].length
var store = _arr[i].toString(2).slice(7 - bytesLength)
for (var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2)
}
str += String.fromCharCode(parseInt(store, 2))
i += bytesLength - 1
} else {
str += String.fromCharCode(_arr[i])
}
}
return str
}
2. 字符串转字节
export const stringToArrayBuffer = function (str) {
// 首先将字符串转为16进制
let val = ''
for (let i = 0; i < str.length; i++) {
if (val === '') {
val = str.charCodeAt(i).toString(16)
} else {
val += ',' + str.charCodeAt(i).toString(16)
}
}
// 将16进制转化为ArrayBuffer
return new Uint8Array(
val.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
})
).buffer
}
注意事项
微信小程序 NFC 功能暂仅支持安卓设备
读取 NFC 数据 uri 格式时,返回内容前缀会存在空字符(\u0000),可根据需要自行转换移除
export const removeNullCharacter = str => {
return str.replace(/\\u([0-9]|[a-fA-F])([0-9]|[a-fA-F])([0-9]|[a-fA-F])([0-9]|[a-fA-F])/g, '')
uniapp调用微信小程序API
helloWX() {
//#ifdef MP-WEIXIN
const systemSetting = wx.getSystemSetting();
console.log(systemSetting.wifiEnabled);
//#endif
let UserInfo = _self.appdata.GetUserInfo();
console.log(UserInfo);
},
组件slot使用
有时我们需要多个插槽, 元素有一个特殊的 attribute:name,语法: ,用来定义额外的插槽
一个不带 name 的 出口会带有隐含的名字“default”
在向具名插槽提供内容的时候,我们可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称
v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header
v-slot 只能添加在 上 ,绑定在其他元素上用slot=“**”
slot-two组件:
<template>
<view class="slottwo">
<view>slottwo</view>
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</view>
</template>
父组件引用slot-two组件:
<view class="slot-item">
<slot-two>
<view>default:没有指定name的默认插槽的内容</view>
<template v-slot:header>
<text>header:我是name为header的slot</text>
</template>
<text slot="footer">footer:我是name为footer的slot</text>
</slot-two>
</view>
JS ArrayBuffer使用
ArrayBuffer
创建 ArrayBuffer
const buff = new ArrayBuffer(4)
这样就创建了一个 4 字节(Byte)长度的内存片段,初始值都是 0。
从 Blob 对象和 File 对象也可以获得 ArrayBuffer:
const blob = new Blob(['123']);
const buff = await blob.arrayBuffer()
ArrayBuffer 的属性和方法
ArrayBuffer
自身只有一个属性:
byteLength
,返回它里面数据的字节数(也就是内存使用量)。
const buff = new ArrayBuffer(4)buff.byteLength// 4
ArrayBuffer
自身只有一个方法:
slice
,用法和数组的
slice
一样,用于拷贝一段内存。
buff.slice(1,3)// ArrayBuffer(2)// 拷贝了 buff 里下标 1、2 的内存数据
slice
方法不会修改原数组。拷贝的数据里包含 start 参数的下标,不包含 end 参数的下标。
ArrayBuffer 不能直接读写
ArrayBuffer
只是存放数据的容器,不能直接对里面的内存数据进行读写。
这是因为操作二进制数据时可以使用多种数据类型,它们的字节长度、值的范围都不尽相同。如果不指定类型,就不能读写内存数据。
ArrayBuffer
支持使用以下 9 种类型来读写内存数据:
Int8
8位带符号整数
signed char
Uint8
8位不带符号整数
unsigned char
Uint8C
8位不带符号整数(自动过滤溢出)
unsigned char
Int16
16位带符号整数
short
Uint16
16位不带符号整数
unsigned short
Int32
32位带符号整数
int
Uint32
32位不带符号的整数
unsigned int
Float32
32位浮点数
float
Float64
64位浮点数
double
我们需要使用
TypedArray
或
DataView
视图来指定类型,这样才能读写ArrayBuffer 里的数据。
创建 DataView
DataView
只有对内存的读、写操作,而且要使用指定的方法
DataView 读内存
DataView
实例提供 8 个方法读取内存。
getInt8
读取 1 个字节,返回一个 8 位整数。
getUint8
读取 1 个字节,返回一个无符号的 8 位整数。
getInt16
读取 2 个字节,返回一个 16 位整数。
getUint16
读取 2 个字节,返回一个无符号的 16 位整数。
getInt32
读取 4 个字节,返回一个 32 位整数。
getUint32
读取 4 个字节,返回一个无符号的 32 位整数。
getFloat32
读取 4 个字节,返回一个 32 位浮点数。
getFloat64
读取 8 个字节,返回一个 64 位浮点数。
const view = new DataView(buff);
view.getUint8(0);
view.getUint16(1);
第一个参数是读取的内存的位置;
第二个参数是可选参数,用来指定字节序。只有当一次性读取超过 1 字节时才有这个参数。
DataView 写内存
DataView
写内存的方法也是 8 个,与读内存的 8 个方法对应。
写方法带有三个参数,第一个参数指明要从哪个字节开始写入,第二个参数为写入的数据。对于那些写入两个或两个以上字节的方法,需要指定第三个参数,false或undefined表示使用大端字节序写入,true表示使用小端字节序写入。
setInt8
写入 1 个字节的 8 位整数。
setUint8
写入 1 个字节的 8 位无符号整数。
setInt16
写入 2 个字节的 16 位整数。
setUint16
写入 2 个字节的 16 位无符号整数。
setInt32
写入 4 个字节的 32 位整数。
setUint32
写入 4 个字节的 32 位无符号整数。
setFloat32
写入 4 个字节的 32 位浮点数。
setFloat64
写入 8 个字节的 64 位浮点数。
const view = new DataView(buff)// DataView.setInt8(byteOffset: number, value: number): void
view.setInt8(0, 0xbb)// DataView.setInt16(byteOffset: number, value: number, littleEndian?: boolean | undefined): void
view.setInt16(4, 1, true)
view.setInt32(8, 520, true)
DataView 的属性
buffer: 操作的ArrayBuffer对象。
byteOffset:起始位置的偏移量
byteLength:字节长度,也就是内存使用量。
uniapp 中 ArrayBuffer 和 json 格式如何互转
第一种:
data为`arraybuffer` 数据
// 使用TextDecoder
var enc = new TextDecoder("utf-8");
var uint8_msg = new Uint8Array(data);
console.log(enc.decode(uint8_msg));
// 再转换为json
第二种:
let content = file.data;//arraybuffer类型数据
let resBlob = new Blob([content])
let reader = new FileReader()
reader.readAsText(resBlob, "utf-8")
reader.onload = () => {
let res = JSON.parse(reader.result)
console.log(JSON.parse(reader.result))
}
微信小程序现在可以用出了443以外别的端口
可以配置端口,如 https://myserver.com:8080,但是配置后只能向 https://myserver.com:8080 发起请求。如果向 https://myserver.com、https://myserver.com:9091 等 URL 请求则会失败。
如果不配置端口。如 https://myserver.com,那么请求的 URL 中也不能包含端口,甚至是默认的 443 端口也不可以。如果向 https://myserver.com:443 请求则会失败。
域名必须经过 ICP 备案;
出于安全考虑,api.weixin.qq.com 不能被配置为服务器域名,相关API也不能在小程序内调用。 开发者应将 AppSecret 保存到后台服务器中,通过服务器使用 getAccessToken 接口获取 access_token,并调用相关 API;
对于每个接口,分别可以配置最多 20 个域名。
非 h5 平台 :key 不支持表达式
这个问题到现在还有,还是不支持key使用复杂表达式。
可以通过方法返回key值解决
methods: {
secondKey(first, second){
return `${first}_${second}`
},
}
:key="secondKey(propItem,item)"
组件安装编写
传统vue组件,需要安装、引用、注册,三个步骤后才能使用组件。easycom将其精简为一步。
只要组件安装在项目的components目录下或uni_modules目录下,并符合components/组件名称/组件名称.vue目录结构。就可以不用引用、注册,直接在页面中使用。
easycom是自动开启的,不需要手动开启。
如果你的组件名称或路径不符合easycom的默认规范,可以在pages.json的easycom节点进行个性化设置,自定义匹配组件的策略。
自定义组件以及使用
目录结构如下:
<template>
<view class="my-componet-box">
{{title}}
</view>
</template>
<script>
export default {
// 声明组件名
name:"my-componet",
data() {
return {
title:'我是自定义组件!'
};
}
}
</script>
<style>
.my-componet-box{
width: 100rpx;
height: 120rpx;
background-color: green;
}
</style>
3.1局部注册
局部注册(仅在注册页面使用,示例在test页面中使用):
<template>
<view>
<!-- 3.使用组件 -->
<my-componet></my-componet>
</view>
</template>
<script>
// 1.导入组件
import myComponet from '@/components/my-componet/my-componet.vue'
export default {
// 2.注册组件
components:{
myComponet
},
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>
效果展示,test页面中显示自定义组件:
3.2全局注册
全局注册(各个页面均可使用):
main.js中:
import Vue from 'vue'
import myComonet from '@/components/my-component'
Vue.component('my-comonet',myComonet )
示例在mine页面中使用(直接使用即可,无需导入和注册):
<template>
<view>
<my-componet></my-componet>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
展示效果:
uniapp使用OpenCV
使用方法
放置代码 下载ZIP压缩包,解压得到opencv文件夹,将整个文件夹放置在/static/js/目录下面
使用示例 前端代码
<view class="panel__bd">
<image class="inputImage" :src="imageUrl" mode="" style="width:750rpx;height:425rpx;"></image>
<canvas id="canvas" type="2d" class="visibleCanvas" style="width:750rpx;height:425rpx;"></canvas>
<view class="button-box">
<button @click="button1" class="marginTop10" >灰度化</button>
<button @click="button2" class="marginTop10" >边缘检测</button>
<button @click="button3" class="marginTop10">特征点检测</button>
<button @click="button4" class="marginTop10">轮廓提取</button>
</view>
</view>
js代码
// 画布
const canvas = 'canvas'
// 示例图片
const imageUrl="http://news.youth.cn/sh/201605/W020160510004428589095.jpg"
// wasm路径
global.wasm_url = '/static/js/opencv/opencv3.4.16.wasm.br'
// opencv_exec.js会从global.wasm_url获取wasm路径
let cv = require('../../static/js/opencv/opencv_exec.js');
export default {
data() {
return {
canvasWidth: 375,
canvasHeight: 236,
// 示例图片
imageUrl: imageUrl,
canDom:"",
}
},
onReady: function (e) {
this.init(canvas)
},
methods: {// 获取画布
init(canvasId) {
let _that = this;
uni.createSelectorQuery()
.select('#' + canvasId)
.fields({ node: true, size: true })
.exec((res) => {
const canvas2d = res[0].node;
// 设置画布的宽度和高度
canvas2d.width = res[0].width;
canvas2d.height = res[0].height;
_that.canDom = canvas2d
});
},
// 创建图像对象
async createImageElement() {
let _that = this;
// 创建2d类型的离屏画布(需要微信基础库2.16.1以上)
let offscreenCanvas = uni.createOffscreenCanvas({type: '2d', width: this.canvasWidth, height: this.canvasHeight});
const image = offscreenCanvas.createImage();
await new Promise(function (resolve, reject) {
image. = resolve;
image.onerror = reject;
image.src = _that.imageUrl
})
// 离屏画布的宽度和高度不能小于图像的
offscreenCanvas.width = this.canvasWidth;
offscreenCanvas.height = this.canvasHeight;
// draw image on canvas
let ctx = offscreenCanvas.getContext('2d')
ctx.drawImage(image, 0, 0, this.canvasWidth, this.canvasHeight);
// get image data from canvas
let imgData = ctx.getImageData(0, 0, this.canvasWidth, this.canvasHeight);
return imgData;
},
async button1() {
let _that = this;
// 将图像转换为ImageData
let imageData = await _that.createImageElement()
// _that.imgProcess1(image1Data, _that.canDom)
// 读取图像
let src = cv.imread(imageData);
let dst = new cv.Mat();
// 灰度化
cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY, 0);
// 显示图像
cv.imshow(_that.canDom, dst);
// 回收对象
src.delete();
dst.delete()
},
async button2() {
// 同上
let _that = this;
const imageData = await _that.createImageElement()
let src = cv.imread(imageData);
let dst = new cv.Mat();
// 灰度化
cv.cvtColor(src, src, cv.COLOR_BGR2GRAY, 0);
let ksize = new cv.Size(5, 5)
cv.GaussianBlur(src, src, ksize, 0, 0)//高斯模糊
let esize = new cv.Size(3, 3)
let element = cv.getStructuringElement(cv.MORPH_RECT, esize)
cv.dilate(src, src, element) //实现过程中发现,适当的膨胀很重要
let low = Math.ceil(src.cols / 30)
cv.Canny(src, dst, 30, 100, 3) //边缘提取
cv.imshow(_that.canDom, dst);
src.delete();
dst.delete()
},
async button3() {
// 同上
let _that = this;
const imageData =await _that.createImageElement()
// _that.imgProcess3(imageData, _that.canDom)
let src = cv.imread(imageData);
let dst = new cv.Mat();
// 灰度化
cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
let orb = new cv.ORB();
let keypoints = new cv.KeyPointVector();
let descriptors = new cv.Mat();
// 特征点
orb.detect(src, keypoints)
// 特征点的描述因子
orb.compute(src, keypoints, descriptors)
// 绘制特征点
cv.drawKeypoints(src, keypoints, dst)
cv.imshow(_that.canDom, dst);
src.delete();
dst.delete()
},
async button4() {
let ratio = 1
// 同上
let _that = this;
const imageData = await _that.createImageElement()
let src = cv.imread(imageData);
let mat = src.clone()
// 灰度化
cv.cvtColor(src, src, cv.COLOR_BGR2GRAY, 0);
let ksize = new cv.Size(5, 5)
cv.GaussianBlur(src, src, ksize, 0, 0)//高斯模糊
let esize = new cv.Size(3, 3)
let element = cv.getStructuringElement(cv.MORPH_RECT, esize)
cv.dilate(src, src, element) //实现过程中发现,适当的膨胀很重要
let low = Math.ceil(src.cols / 30)
cv.Canny(src, src, 30, 100, 3) //边缘提取
let contours = new cv.MatVector()
let contours2 = new cv.MatVector()
let hierarchy = new cv.Mat()
cv.findContours(src, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
//轮廓筛选
let parentIdx = -1
let max_aera = 0
let contours3 = new cv.MatVector()
for (let i = 0; i < contours.size(); i++) {
let cnt = contours.get(i)
let area = Math.abs(cv.contourArea(cnt))
if (max_aera < area) {
max_aera = area
parentIdx = i
}
}
if (parentIdx != -1) {
let color = new cv.Scalar(0, 255, 0, 255)
let mat3 = cv.Mat.zeros(src.rows / ratio, src.cols / ratio, cv.CV_8UC3)
//找到定位点信息
contours2.push_back(contours.get(parentIdx))
let points = []
let points2 = []
let cnt = contours2.get(0)
let tmp = new cv.Mat()
let cnt_len = cv.arcLength(cnt, true) //计算轮廓周长
cv.approxPolyDP(cnt, tmp, 0.02 * cnt_len, true) //多边形逼近
for (let i = 0; i < tmp.total(); i++) {
points[i] = { x: points2[i * 2], y: points2[i * 2 + 1] }
// cv.circle(mat3, points[i], 3, color, 1) //画顶点
}
// 绘制轮廓
// cv.drawContours(mat3, contours2, 0, color, cv.FILLED)
cv.drawContours(mat3, contours2, 0, color, 2)
cv.imshow(_that.canDom, mat3)
mat3.delete()
} else {
console.log('识别失败')
}
src.delete();
},
}
}
css代码
.title{
width: 750rpx;
height: 60rpx;
background-color: #00aa00;
font-size: 30rpx;
text-align:center;
margin-top: 20rpx;
// margin-bottom: 20rpx;
color: #ffffff;
}
.visibleCanvas{
margin-top: 25rpx;
}
.button-box{
width: 750rpx;
height: 80rpx;
padding-top: 10rpx;
button{
width: 600rpx;
height: 70rpx;
font-size: 24rpx;
background-color: #4CD964;
color: #ffffff;
// line-height: 50rpx;
border-radius: 50rpx;
margin-top: 20rpx;
}
}
uniapp中通过js动态添加UI组件比如按钮
在 uni-app 中通过 JavaScript 动态添加 UI 组件(如按钮)可以通过以下几种方式实现:
使用数据驱动的方式(推荐):
这是最符合 Vue 和 uni-app 开发理念的方式,通过改变数据来动态渲染组件。
<template>
<view>
<button v-for="(btn, index) in buttons" :key="index" @click="btn.onClick">
{{ btn.text }}
</button>
<button @click="addButton">添加按钮</button>
</view>
</template>
<script>
export default {
data() {
return {
buttons: []
}
},
methods: {
addButton() {
this.buttons.push({
text: `按钮 ${this.buttons.length + 1}`,
onClick: () => {
console.log(`按钮 ${this.buttons.length} 被点击`)
}
})
}
}
}
</script>
使用 $refs 和 DOM 操作:
虽然这种方式不太符合 Vue 的数据驱动理念,但在某些特殊情况下可能会用到。
<template>
<view>
<view ref="buttonContainer"></view>
<button @click="addButton">添加按钮</button>
</view>
</template>
<script>
export default {
methods: {
addButton() {
// 创建按钮元素
const button = document.createElement('button')
button.innerText = '新按钮'
button.onclick = () => {
console.log('新按钮被点击')
}
// 将按钮添加到容器中
this.$refs.buttonContainer.appendChild(button)
}
}
}
</script>
注意:这种方法在小程序环境中可能不适用,因为小程序不支持直接操作 DOM。
使用动态组件:
如果你需要动态添加更复杂的组件,可以使用 Vue 的动态组件功能。
<template>
<view>
<component v-for="(comp, index) in dynamicComponents"
:key="index"
:is="comp.component"
v-bind="comp.props">
</component>
<button @click="addDynamicButton">添加动态按钮</button>
</view>
</template>
<script>
import DynamicButton from './DynamicButton.vue'
export default {
components: {
DynamicButton
},
data() {
return {
dynamicComponents: []
}
},
methods: {
addDynamicButton() {
this.dynamicComponents.push({
component: 'DynamicButton',
props: {
text: `动态按钮 ${this.dynamicComponents.length + 1}`,
onClick: this.handleDynamicButtonClick
}
})
},
handleDynamicButtonClick() {
console.log('动态按钮被点击')
}
}
}
</script>
在这个例子中,DynamicButton 是一个自定义的按钮组件。
推荐使用第一种方法(数据驱动),因为它最符合 Vue 和 uni-app 的开发理念,并且能够在所有平台上正常工作。其他方法可能在某些平台(特别是小程序)上存在限制或不兼容的情况。
uniapp动态界面生成
通过数据生成
现在有个需求,界面是由不同的数据组成,数据里有按钮图片编辑文本等UI组件,给个例子里面包括多种数据
为了满足你的需求,我们需要创建一个页面,其中UI组件(如按钮、图片、编辑文本等)是根据后端传来的数据动态生成的。这里我将给出一个示例,展示如何在uni-app中根据不同的数据类型动态生成UI组件。
示例结构
我们将创建一个页面,该页面接收一个JSON数组作为数据源,数组中的每个对象代表一个UI组件,包含组件类型和相关属性。
JSON 数据示例
json
[
{
"type": "text",
"content": "欢迎使用uni-app!"
},
{
"type": "image",
"src": "https://example.com/image.jpg"
},
{
"type": "button",
"text": "点击我",
"type": "primary"
},
{
"type": "input",
"placeholder": "请输入文字"
}
]
步骤 1: 创建页面
在 pages 目录下创建一个新的页面,例如 dynamic-ui.vue。
步骤 2: 编写代码
接下来,在 dynamic-ui.vue 文件中编写如下代码:
vue
<template>
<view class="container">
<block v-for="(item, index) in components" :key="index">
<view v-if="item.type === 'text'">{{ item.content }}</view>
<image v-if="item.type === 'image'" :src="item.src" mode="aspectFit"></image>
<button v-if="item.type === 'button'" @click="handleButtonClick(item.text)" :type="item.buttonType">{{ item.text }}</button>
<input v-if="item.type === 'input'" :placeholder="item.placeholder" />
</block>
</view>
</template>
<script>
export default {
data() {
return {
components: [
{ type: 'text', content: '欢迎使用uni-app!' },
{ type: 'image', src: 'https://example.com/image.jpg' },
{ type: 'button', text: '点击我', buttonType: 'primary' },
{ type: 'input', placeholder: '请输入文字' }
]
};
},
methods: {
handleButtonClick(text) {
uni.showToast({
title: `点击了 ${text}`,
duration: 2000
});
}
}
};
</script>
<style scoped>
.container {
padding: 20px;
}
image {
width: 100%;
height: auto;
}
</style>
代码解释
模板部分:
使用 v-for 遍历 components 数组中的每一项,并为每个组件设置唯一的 key 属性。
根据 item.type 的值使用不同的条件渲染逻辑来显示对应的组件。
对于按钮,使用 @click 绑定点击事件处理函数 handleButtonClick。
脚本部分:
在 data 中定义了一个 components 数组,用于存储不同类型的UI组件信息。
handleButtonClick 方法用于处理按钮被点击时的行为。
样式部分:
简单设置了容器的内边距和图片的宽度与高度。
测试
保存文件后,在模拟器或真机上运行这个页面,你会看到根据数据动态生成的不同UI组件。
这个示例展示了如何根据不同的数据类型动态生成UI组件。如果你需要支持更多的组件类型或者有更复杂的交互逻辑,可以根据同样的模式扩展此示例。如果有其他问题或需要进一步的帮助,请随时告诉我!
uniapp如何实现最新发布apk版本检查和更新
一、首页版本检查并提示
首先uniapp跳过登录页面或者授权登录页面之后,就会进入首页,我们可以在App.vue中写入要检查版本号的相关提示操作。
<script>
export default {
// 此处globalData为了演示其作用,不是uView框架的一部分
data() {
return {
version: 100, //注意你在uniapp中打包(原生app-云打包)时候的初始版本号
}
},
methods: {
androidCheckUpdate() {
let _this = this;
uni.request({
url: 'https://errong.com/smartAppversion.json?_t='+ new Date().getTime(), //这个是把你apk部署外网服务器的tomcat能访问下载的地址,具体你根据自己的来,然后smartAppversion.json是存放你最新版本好的下载地址啥的和apk关联起来,下面我会展示出代码
data: {},
header: {},
success: (res) => {
if (res.data.code === 0) {
if (res.data.version > _this.version) { // 如果最新版本大于现在已经安装的App的版本
// 新建下载任务
var dtask = plus.downloader.createDownload(res.data.url, {
force: true
}, function(d, status) {
// 下载完成
if (status == 200) {
uni.showModal({
title: '下载完成,即将安装',
showCancel: false,
success: () => {
// 由于install只能安装本地的地址,所以先把下载的地址在本地找到,再调用install
plus.runtime.install(plus.io
.convertLocalFilesSystemURL(d
.filename), {},
function() {
console.log('success');
plus.runtime
.restart(); // 安装成功后重启
},
function(error) {
console.log(error.message);
uni.showToast({
title: "安装失败",
duration: 1500
})
})
}
})
} else {
uni.showToast({
title: '更新失败',
duration: 1500
})
}
})
dtask.start();
} else {
//如果是最新版本了,进入首页就会提示一下,建议屏蔽掉
uni.showModal({
title: '当前已是最新版本',
showCancel: false
})
}
}
}
})
}
},
onLaunch() {
// #ifdef APP-PLUS
plus.runtime.getProperty(plus.runtime.appid, (wgtinfo) => {
this.version = wgtinfo.versionCode;
})
this.androidCheckUpdate(); // app中检测版本更新
// #endif
// 1.1.0版本之前关于http拦截器代码,已平滑移动到/common/http.interceptor.js中
// 注意,需要在/main.js中实例化Vue之后引入如下(详见文档说明):
// import httpInterceptor from '@/common/http.interceptor.js'
// Vue.use(httpInterceptor, app)
// process.env.VUE_APP_PLATFORM 为通过js判断平台名称的方法,结果分别如下:
/**
* h5,app-plus(nvue下也为app-plus),mp-weixin,mp-alipay......
*/
}
}
</script>
二、点击更新按钮检查版本和下载更新apk
这个针对的是用户点击按钮可以实现本地版本是不是最新的版本,如果是就不会下载最新的版本,并提示您的版本已经是最新的;如果不是就会下载安装更新你安卓本地的应用!
你可以在我的信息页面中加入一个按钮,用于检查更新旧版本,微信小程序中没啥必要:
<!-- #ifdef APP-PLUS -->
<div class="section" @click="handleCheckVersion">
<div>
<u-icon name="checkmark-circle-fill" size="20" style="margin-right: 5px;"></u-icon>检查更新
</div>
<span><u-loading v-if="isCheckVersion" mode="flower"></u-loading></span>
</div>
<!-- #endif -->
然后:
<script>
export default {
data() {
return {
innerVer: null,
version: null,
isCheckVersion: false,
}
},
methods: {
// 检查版本更新
handleCheckVersion() {
let _this = this;
// #ifdef APP-PLUS
_this.isCheckVersion = true;
plus.screen.lockOrientation('portrait-primary') // 竖屏锁定
plus.runtime.getProperty(plus.runtime.appid, (widgetInfo) => {
_this.innerVer = widgetInfo.version;
_this.version = widgetInfo.versionCode;
uni.request({
url: 'https://errong.com/smartAppversion.json?_t='+ new Date().getTime(), //版本检测
data: {},
header: {},
success: (result) => {
if (result.data.code === 0) {
setTimeout(() => {
_this.isCheckVersion = false;
}, 1000);
console.log(result.data.version,_this.version);
if (result.data.version - _this.version > 0) { // 如果最新版本大于现在已经安装的App的版本
uni.showModal({
title: "更新提示",
content: "发现新版本,请确认下载更新?",
success: (res) => {
if (res.confirm) {
uni.showLoading({
title: '更新中...'
});
uni.downloadFile({
url: result.data.url,
success: (downloadResult) => {
uni.hideLoading();
if (downloadResult.statusCode === 200) {
plus.runtime.install(downloadResult.tempFilePath, {
force: true
}, function() {
console.log('App安装成功!');
uni.showToast('App安装成功!', 'success');
plus.runtime.restart();
}, function(e) {
console.log('App安装失败!');
})
}
}
});
}
}
})
} else {
_this.$showToast("当前已是最新版本V" + _this.innerVer, "none");
}
}
}
})
})
// #endif
},
},
}
</script>
三、服务器中tomcat部署文件
tomcat下载地址同级别直接丢入这两个文件:android_smartAppV1.0.apk、smartAppversion.json(和前面代码注意保持一致)
json文件的代码如下:
{
"code": 0,
"msg": "success",
"version": 206,
"url": "https://errong.com/android_smartAppV1.0.apk"
}
以上就是uniapp的安卓apk中检查更新的全部了!感觉有用的话可以点个关注+点赞+收藏!
uniapp 读NFC的ID
在 Uniapp 中使用 NFC 功能通常涉及到调用原生 API,因为 NFC 主要是硬件级别的功能。下面是一个使用 Uniapp 读取 NFC 标签 ID 和进行读写的示例。
准备工作
安装 Uniapp 开发环境:
安装 Node.js 和 HBuilderX。
创建一个新的 Uniapp 项目。
添加 NFC 相关的权限:
对于 Android 平台,在 manifest.json 文件中添加 NFC 相关的权限。
对于 iOS 平台,由于 iOS 不开放 NFC 标准读写功能,因此主要关注 Android 平台。
示例代码
假设你已经有了一个基本的 Uniapp 项目,接下来我们将添加读取 NFC 标签 ID 和读写 NFC 的功能。
1. 添加 NFC 相关的权限
在 manifest.json 文件中添加 NFC 相关的权限:
json
{
"plus": {
"nfc": {
"enabled": true
}
}
}
2. 编写 JavaScript 代码
在你的页面 .js 文件中,添加以下代码来读取 NFC 标签 ID 和进行读写操作:
javascript
import { on, off } from '@dcloudio/uni-app';
export default {
data() {
return {
tagId: '',
isScanning: false,
errorMessage: ''
};
},
methods: {
startScan() {
this.isScanning = true;
this.errorMessage = '';
this.tagId = '';
on('nfc', (event) => {
console.log('NFC event:', event);
if (event.type === 'tagDetected') {
this.tagId = event.tag.id;
this.isScanning = false;
this.readTag(event.tag);
}
});
on('nfcError', (error) => {
console.error('NFC error:', error);
this.errorMessage = error.message;
this.isScanning = false;
});
uni.startNFC();
},
stopScan() {
this.isScanning = false;
off('nfc');
off('nfcError');
uni.stopNFC();
},
async readTag(tag) {
try {
const techList = tag.techList;
const ndef = techList.find((tech) => tech === 'android.nfc.tech.Ndef');
if (ndef) {
const ndefTech = await tag.connect(ndef);
const records = await ndefTech.read();
console.log('NDEF records:', records);
ndefTech.close();
} else {
console.log('No NDEF technology found.');
}
} catch (error) {
console.error('Error reading tag:', error);
}
},
async writeTag(tag) {
try {
const techList = tag.techList;
const ndef = techList.find((tech) => tech === 'android.nfc.tech.Ndef');
if (ndef) {
const ndefTech = await tag.connect(ndef);
const message = new ndef.Message([new ndef.TextRecord('Hello, NFC!')]);
await ndefTech.write(message);
ndefTech.close();
} else {
console.log('No NDEF technology found.');
}
} catch (error) {
console.error('Error writing tag:', error);
}
}
}
};
3. 更新页面模板
在你的页面 .vue 文件中,更新模板以显示 NFC 标签 ID 和操作按钮:
html
<template>
<view>
<button @click="startScan">Start Scan</button>
<button v-if="tagId" @click="stopScan">Stop Scan</button>
<button v-if="tagId" @click="readTag">Read Tag</button>
<button v-if="tagId" @click="writeTag">Write Tag</button>
<text v-if="tagId">Tag ID: {{ tagId }}</text>
<text v-if="errorMessage">{{ errorMessage }}</text>
</view>
</template>
注意事项
上面的示例代码仅适用于 Android 平台,因为 iOS 幷不开放标准的 NFC 读写功能。
确保你的设备支持 NFC,并且开启了 NFC 功能。
请根据实际需求调整代码,例如错误处理和界面更新。
以上就是使用 Uniapp 进行 NFC 标签读写的基本示例。如果你需要进一步的帮助或有其他问题,请随时提问。
uniapp读取nfc
<template>
<view>
<view class="uni-padding-wrap">
NFC
<view class="uni-common-mt" style="background:#FFF; padding:20upx;">
<text>
UID:{{UID}}
{{tip}}
</text>
</view>
</view>
</view>
</template>
<script>
var NfcAdapter;
export default {
data() {
return {
title: 'redNFC',
UID: '',
msg: '',
tip: '',
}
},
onLoad() {
console.log("onLoad");
},
onShow() {
console.log("onShow");
this.NFCInit();
},
onHide() {
console.log("onHide");
this.NFCReadUID();
},
methods: {
NFCInit() {
try {
var main = plus.android.runtimeMainActivity();
//console.log(main);
var Intent = plus.android.importClass('android.content.Intent');
// console.log(Intent);
var Activity = plus.android.importClass('android.app.Activity');
//console.log(Activity);
var PendingIntent = plus.android.importClass('android.app.PendingIntent');
// console.log(PendingIntent);
var IntentFilter = plus.android.importClass('android.content.IntentFilter');
// console.log(IntentFilter);
// var Uri = plus.android.importClass('android.net.Uri');
// var Bundle = plus.android.importClass('android.os.Bundle');
// var Handler = plus.android.importClass('android.os.Handler');
//console.log(Handler);
NfcAdapter = plus.android.importClass('android.nfc.NfcAdapter');
//console.log(NfcAdapter);
var _nfcAdapter = NfcAdapter.getDefaultAdapter(main)
// console.log(_nfcAdapter);
var ndef = new IntentFilter("android.nfc.action.NDEF_DISCOVERED"); //NfcAdapter.ACTION_NDEF_DISCOVERED
// console.log(ndef);
var tag = new IntentFilter("android.nfc.action.TAG_DISCOVERED"); //NfcAdapter.ACTION_TECH_DISCOVERED
// console.log(tag);
var tech = new IntentFilter("android.nfc.action.TECH_DISCOVERED");
// console.log(tech);
var intentFiltersArray = [ndef, tag, tech];
var techListsArray = [
["android.nfc.tech.Ndef"],
["android.nfc.tech.IsoDep"],
["android.nfc.tech.NfcA"],
["android.nfc.tech.NfcB"],
["android.nfc.tech.NfcF"],
["android.nfc.tech.Nfcf"],
["android.nfc.tech.NfcV"],
["android.nfc.tech.NdefFormatable"],
["android.nfc.tech.MifareClassi"],
["android.nfc.tech.MifareUltralight"]
];
var _intent = new Intent(main, main.getClass());
// console.log(_intent);
_intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
var pendingIntent = PendingIntent.getActivity(main, 0, _intent, 0);
// console.log(pendingIntent);
if (_nfcAdapter == null) {
this.tip = '本设备不支持NFC!';
} else if (_nfcAdapter.isEnabled() == false) {
this.tip = 'NFC功能未打开!';
} else {
this.tip = 'NFC正常';
_nfcAdapter.enableForegroundDispatch(main, pendingIntent, IntentFilter, techListsArray);
}
} catch (e) {
//TODO handle the exception
}
},
NFCReadUID() {
var main = plus.android.runtimeMainActivity();
var _intent = main.getIntent();
var _action = _intent.getAction();
// console.log("action type:" + _action);
if (NfcAdapter.ACTION_NDEF_DISCOVERED == _action || NfcAdapter.ACTION_TAG_DISCOVERED == _action || NfcAdapter.ACTION_TECH_DISCOVERED ==
_action) {
var Tag = plus.android.importClass('android.nfc.Tag');
var tagFromIntent = _intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
var uid = _intent.getByteArrayExtra(NfcAdapter.EXTRA_ID);
console.log(uid);
this.UID = this.Bytes2HexString(uid);
//console.log(this.UID);
}
},
//将byte[] 转为Hex,
Bytes2HexString(arrBytes) {
var str = "";
for (var i = 0; i < arrBytes.length; i++) {
var tmp;
var num = arrBytes[i];
if (num < 0) {
//Java中数值是以补码的形式存在的,应用程序展示的十进制是补码对应真值。补码的存在主要为了简化计算机底层的运算,将减法运算直接当加法来做
tmp = (255 + num + 1).toString(16);
} else {
tmp = num.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
}
}
</script>
<style>
</style>
加权限
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.NFC\"/>",
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>"
小程序连接蓝牙打印机,输出结果是指令串
后端传输给前端打印指令串(CPCL指令集)
前端处理后将数据传递给打印机,发现打印机直接将指令识别为打印内容输出了
例如下面一段伪代码 执行后结果直接打印"! 0 200 200 210 1 T 4 0 30 40 Hello World PRINT"
let printData = string2buffer("! 0 200 200 210 1 T 4 0 30 40 Hello World PRINT");
printTemplate(deviceId, serviceId, characteristicId, printData);
string2buffer:function (str) {
// 首先将字符串转为16进制
let val =""
for (let i = 0; i < str.length; i++) {
if (val ==='') {
val = str.charCodeAt(i).toString(16)
}else {
val +=',' + str.charCodeAt(i).toString(16)
}
}
// 将16进制转化为ArrayBuffer
return new Uint8Array(val.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
})).buffer
}
CPCL指令示例:
上一篇:uniapp快速回顾