项目中使用uniapp开发安卓APP,有一个需求需要自定义相机界面,拍照点击确定后上传到服务器,同时不退出相机界面上传成功后继续拍摄下一张
先看看呈现效果:
实现功能点:
其中功能点包含:打开相机、获取摄像头、默认打开后置摄像头、切换摄像头、自定义相机界面,关闭相机、打开闪光灯,压缩图片等
实现思路:
原理是在app内部打开一个web-view,在web-view中使用navigator.mediaDevices.getUserMedia获取到摄像头,并使用window.URL.createObjectURL(stream)将摄像头的视频流传到页面中的video标签,达到相机的效果,同时定义界面样式,我这里是定义了类似相机的样式,也可添加蒙版图片做成人脸识别的效果
点击拍照,使用canvas元素 context.drawImage 绘画出video中的图片,并显示canvas元素,隐藏video元素
点击完成,将图片转成base64格式
通过uni.postMessage传到父组件,在父组件中调用上传接口,上传,上传成功后修改url中的参数传到web-view中更新状态,判断是否最后一张,如果不是则拍摄下一张,是则退出拍照界面
实现代码如下
目录结构:
src\hybrid\html\index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>调用摄像头拍照</title>
<!-- <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> -->
<script type="text/javascript" src="./js/jquery.js"></script>
<link rel="stylesheet" href="./css/index.css" />
<script>
// 是否是手机
function isMobile() {
let userAgentInfo = navigator.userAgent;
let mobileAgents = [
"Android",
"iPhone",
"SymbianOS",
"Windows Phone",
"iPad",
"iPod",
];
let mobile_flag = false;
//根据userAgent判断是否是手机
for (let v = 0; v < mobileAgents.length; v++) {
if (userAgentInfo.indexOf(mobileAgents[v]) > 0) {
mobile_flag = true;
break;
}
}
let screen_width = window.screen.width;
let screen_height = window.screen.height;
//根据屏幕分辨率判断是否是手机
if (screen_width > 325 && screen_height < 750) {
mobile_flag = true;
}
console.log(mobile_flag, "mobile_flag");
return mobile_flag;
}
/**
* @description 本地图片转base64方法(兼容APP、H5、小程序)
* @param {number} path 图片本地路径
* @returns Promise对象
*/
function toBase64(path) {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
plus.io.resolveLocalFileSystemURL(path, (entry) => {
entry.file((file) => {
let fileReader = new plus.io.FileReader();
fileReader.readAsDataURL(file);
fileReader.onloadend = (evt) => {
console.log(evt, "evt");
let base64 = evt.target.result.split(",")[1];
resolve(base64);
};
});
});
});
}
</script>
</head>
<body>
<div id="loading" class="loading">
<div class="loading-spinner"></div>
<div class="loading-text">上传中...</div>
</div>
<div id="navbar" class="nav">
<span id="backIcon" class="back-icon">
<svg
t="1740124748321"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2597"
width="160"
height="80">
<path
d="M872.802928 755.99406 872.864326 755.99406 872.864326 755.624646Z"
fill="#e6e6e6"
p-id="2598"></path>
<path
d="M927.846568 511.997953c0-229.315756-186.567139-415.839917-415.838893-415.839917-229.329059 0-415.85322 186.524161-415.85322 415.839917 0 229.300406 186.524161 415.84094 415.85322 415.84094C741.278405 927.838893 927.846568 741.29836 927.846568 511.997953M512.007675 868.171955c-196.375529 0-356.172979-159.827125-356.172979-356.174002 0-196.374506 159.797449-356.157629 356.172979-356.157629 196.34483 0 356.144326 159.783123 356.144326 356.157629C868.152001 708.34483 708.352505 868.171955 512.007675 868.171955"
fill="#e6e6e6"
p-id="2599"></path>
<path
d="M682.378947 642.227993 553.797453 513.264806 682.261267 386.229528c11.661597-11.514241 11.749602-30.332842 0.234337-41.995463-11.514241-11.676947-30.362518-11.765975-42.026162-0.222057L511.888971 471.195665 385.223107 344.130711c-11.602246-11.603269-30.393217-11.661597-42.025139-0.059352-11.603269 11.618619-11.603269 30.407544-0.059352 42.011836l126.518508 126.887922L342.137823 639.104863c-11.662621 11.543917-11.780301 30.305213-0.23536 41.96988 5.830799 5.89015 13.429871 8.833179 21.086248 8.833179 7.53972 0 15.136745-2.8847 20.910239-8.569166l127.695311-126.311801L640.293433 684.195827c5.802146 5.8001 13.428847 8.717546 21.056572 8.717546 7.599072 0 15.165398-2.917446 20.968567-8.659217C693.922864 672.681586 693.950494 653.889591 682.378947 642.227993"
fill="#e6e6e6"
p-id="2600"></path>
</svg>
</span>
<div id="navTitle" class="nav-title">拍照上传</div>
<span id="switchCameraIcon" class="switch-camera-icon">
<svg
t="1740126202808"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5573"
width="160"
height="80">
<path
d="M837.096193 221.735104h-74.30738l-20.898982-60.375066c-16.255219-46.442752-60.375066-78.952166-109.1397-78.952167H388.927987c-51.086515 0-95.206362 32.509415-109.139699 78.952167l-20.898983 60.375066h-71.985498c-102.173031 0-185.768961 83.59593-185.76896 185.76896v348.317057c0 102.173031 83.59593 185.768961 185.76896 185.768961h650.192386c102.173031 0 185.768961-83.59593 185.76896-185.768961V407.505088c0-102.173031-83.59593-185.769984-185.76896-185.769984z m116.105344 534.087041c0 65.018829-51.086515 116.105345-116.105344 116.105344H186.903807c-65.018829 0-116.105345-51.086515-116.105344-116.105344V407.505088c0-65.018829 51.086515-116.105345 116.105344-116.105345h123.072013l16.255219-48.764634 20.898983-60.375065c6.966669-18.577101 23.220864-32.509415 44.119847-32.509415h243.822144c20.898982 0 37.154201 13.932314 44.119847 32.509415l20.898982 60.375065 16.255219 48.764634h120.750132c65.018829 0 116.105345 51.086515 116.105344 116.105345v348.317057z m-218.278375-97.529268c-39.476083 88.240717-127.715777 143.970996-222.923162 143.970996-83.59593 0-160.226215-44.119847-204.346061-113.783463l-37.154202 11.610432 32.509415-141.649113 106.816794 99.851148-32.509415 9.288551c32.509415 39.476083 81.274048 65.018829 134.682446 65.018829 69.663616 0 132.360564-39.476083 160.226214-104.494912 6.966669-18.577101 27.865651-25.542746 46.442752-18.577101 16.255219 9.28855 23.220864 30.187533 16.255219 48.764633z m-13.933337-225.245043l34.831296-9.288551-34.831296 134.682446-99.851149-95.206363 30.187533-9.28855c-32.509415-44.119847-83.59593-69.663616-139.327232-69.663616-65.018829 0-123.072013 34.831296-153.259546 92.88448-9.28855 16.255219-30.187533 23.220864-46.442752 13.932314s-23.220864-30.187533-13.932314-46.442752c41.797965-78.952166 125.393895-130.038682 215.956493-130.038681 83.596953 0.002047 162.54912 46.443775 206.668967 118.429273z"
fill="#fff"
p-id="5574"></path>
</svg>
</span>
</div>
<div id="container" class="container">
<canvas id="canvas"></canvas>
</div>
<!-- </div> -->
<!-- 蒙版图片 -->
<img id="bgImg" src="" alt="" class="bg-img" />
<div class="mb"></div>
<video id="video" width="100%"></video>
<div class="btmBox">
<span id="albumBtn">
<svg
t="1740198369293"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="7554"
width="200"
height="100">
<path
d="M160 160v704h704V160H160zM128 96h768a31.146667 31.146667 0 0 1 23.04 8.96c5.973333 6.016 8.96 13.696 8.96 23.04v768a31.146667 31.146667 0 0 1-8.96 23.04 31.146667 31.146667 0 0 1-23.04 8.96H128a31.146667 31.146667 0 0 1-23.04-8.96 31.146667 31.146667 0 0 1-8.96-23.04V128a31.146667 31.146667 0 0 1 8.96-23.04 31.146667 31.146667 0 0 1 23.04-8.96z m256 192c42.666667 0 64 21.333333 64 64S426.666667 416 384 416s-64-21.333333-64-64 21.333333-64 64-64zM185.002667 877.013333l-50.048-39.04 216.021333-282.026666a91.733333 91.733333 0 0 1 63.488-35.968 94.208 94.208 0 0 1 70.485333 18.005333l125.013334 100.992a32.256 32.256 0 0 0 23.466666 6.485333 33.749333 33.749333 0 0 0 21.504-11.477333l216.021334-270.037333 50.005333 40.021333-216.021333 270.037333c-16.64 20.650667-38.144 32.298667-64.469334 34.986667a95.402667 95.402667 0 0 1-70.528-20.010667l-123.989333-99.968a32.256 32.256 0 0 0-23.466667-6.528 28.970667 28.970667 0 0 0-20.522666 12.501334l-216.96 282.026666z"
fill="#ffffff"
fill-opacity=".8"
p-id="7555"></path>
</svg>
</span>
<button id="capture">拍照</button>
<button id="finishBtn">完成</button>
</div>
</body>
<!-- uniSdk -->
<script type="text/javascript" src="./js/unisdk.js"></script>
<script type="text/javascript">
let flag = false;
let strType;
// 参数
let urlParams = {};
// 获取dom
const backIcon = document.getElementById("backIcon");
const switchCameraIcon = document.getElementById("switchCameraIcon");
const albumBtn = document.getElementById("albumBtn");
const video = document.getElementById("video"); // video
const allImg = document.getElementById("allImg");
const finishBtn = document.getElementById("finishBtn");
const capture = document.getElementById("capture");
const canvas = document.getElementById("canvas");
const bgImg = document.getElementById("bgImg");
const navTitle = document.getElementById("navTitle");
const navbar = document.getElementById("navbar");
const loadingElement = document.getElementById("loading");
const container = document.getElementById("container");
const context = canvas.getContext("2d");
let currentStream = null;
let currentVideoDeviceIndex = null;
let timer = null;
$(finishBtn).hide();
$(allImg).hide();
$(video).hide();
$(canvas).hide();
$(container).hide();
$(bgImg).hide();
// 获取传惨
function getPramas() {
var params = new URLSearchParams(window.location.search);
urlParams = {
title: params.get("title"),
index: params.get("index"),
titleList: params.get("titleList").split(","),
type: params.get("type"),
// result: params.get("result"),
};
if (params.get("topHight")) {
urlParams.topHight = Number(params.get("topHight")) + 10;
} else {
urlParams.topHight = 0;
}
}
// 更新标题
function updateTitle() {
if (urlParams.title) {
$("#navTitle").html(urlParams.title);
} else {
urlParams.title = "拍照上传";
}
if (urlParams.titleList) {
if (urlParams.titleList && (urlParams.index || urlParams.index == 0)) {
console.log(urlParams.titleList[urlParams.index], "3333");
$("#navTitle").html(urlParams.titleList[urlParams.index]);
} else {
urlParams.title = "拍照上传";
}
}
}
getPramas();
updateTitle();
navbar.style["padding-top"] = `${urlParams.topHight}px`;
// 获取屏幕区域
const screen_W = window.innerWidth || document.body.clientWidth;
const screen_H = window.innerHeight || document.body.clientHeight;
const videoWidth = screen_W;
const videoHeight = screen_H - 298 - 180 - urlParams.topHight;
video.style.height = `${screen_H - 298 - 180 - urlParams.topHight}px`;
// 视频流分辨率设置
let constraints = {
// video: true
video: {
// 前置摄像头&后置摄像头 默认后置摄像头
facingMode: {
exact: "environment",
},
},
};
setTimeout(() => {
canvas.width = screen_W;
canvas.height = videoHeight;
// 判断是否是手机
if (isMobile()) {
$(canvas).css({
width: "100%",
});
canvas.width = screen_W;
canvas.height = videoHeight;
// alert("phone");
}
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// 判断摄像是否开启
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
vargetUserMedia =
navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
if (!getUserMedia) {
returnPromise.reject(
newError("getUserMedia is not implemented in this browser")
);
}
return newPromise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
// 获取摄像头
navigator.mediaDevices
.getUserMedia(constraints)
.then(function (stream) {
$(video).show();
if ("srcObject" in video) {
video.srcObject = stream;
currentStream = stream;
} else {
video.src = window.URL.createObjectURL(stream);
}
video.onloadedmetadata = function (e) {
video.play();
};
})
.catch(function (err) {
var r = confirm("是否授权使用媒体相机采集图片");
if (r == true) {
// 这里可以调用5+ API了,为了更好的兼容性,应该使用以下代码进行判断
if (window.plus) {
var cmr = plus.camera.getCamera();
} else {
// 兼容老版本的plusready事件
document.addEventListener(
"plusready",
function () {
var cmr = plus.camera.getCamera();
},
false
);
}
} else {
uni.webView.navigateBack();
}
});
// 拍照&重新拍照
capture.addEventListener("click", function (e) {
if (!flag) {
$(container).show();
$(canvas).show();
let ratio = video.videoWidth / video.videoHeight;
canvas.height = canvas.width / ratio;
// console.log(canvas.width / ratio, "height");
context.drawImage(video, 0, 0, canvas.width, canvas.width / ratio);
// context.drawImage(video, 50, 185, 400, 280, 50, 280, 880, 450);
$(allImg).show();
$(finishBtn).show();
$(video).hide();
$(capture).text("重新拍照");
flag = true;
} else {
flag = false;
context.clearRect(0, 0, canvas.width, canvas.height);
$(allImg).hide();
$(video).show();
$(finishBtn).hide();
$(container).hide();
$(canvas).hide();
$(capture).text("拍照");
}
});
// 打开相册
albumBtn.addEventListener("click", function (e) {
plus.gallery.pick(
function (path) {
loadingElement.style.display = "flex";
uni.postMessage({
data: {
path: path,
action: "getImagePath",
},
});
// setTimeout(() => {
// if (urlParams.index || urlParams.index == 0) {
// ++urlParams.index;
// }
// }, 500);
console.log(path, "path");
},
function (e) {
console.log("取消选择图片");
},
{
filter: "image",
}
);
});
//确认照片
finishBtn.addEventListener("click", function (e) {
sureImg();
});
// 返回
backIcon.addEventListener("click", function (e) {
// 返回前清除计时器
clearTimer();
uni.postMessage({
data: {
action: "close",
},
});
});
// 切换摄像头
switchCameraIcon.addEventListener("click", switchCamera);
}, 500);
// 切换摄像头
async function switchCamera() {
try {
// 获取当前所有媒体设备
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(
(device) => device.kind === "videoinput"
);
// 如果只有一个视频设备,则无法切换
if (videoDevices.length <= 1) {
console.log("Only one camera available.");
return;
}
// 获取当前活动的流(如果有的话)并停止它
if (currentStream) {
currentStream.getTracks().forEach((track) => track.stop());
}
// 切换摄像头设备
if (constraints.video.facingMode.exact == "user") {
constraints.video.facingMode.exact = "environment"; // 后置摄像头
} else {
constraints.video.facingMode.exact = "user"; // 前置摄像头
}
// 请求新的媒体流
const newStream = await navigator.mediaDevices.getUserMedia(
constraints
);
updateVideoElement(newStream); // 更新视频元素以显示新流
} catch (error) {
console.error("Error switching camera.", error);
}
}
// 更新视频元素以显示新流
function updateVideoElement(stream) {
$(video).show();
if ("srcObject" in video) {
video.srcObject = stream;
} else {
video.src = window.URL.createObjectURL(stream);
}
video.onloadedmetadata = function (e) {
video.play();
};
}
/**
* 确认照片
*/
function sureImg() {
loadingElement.style.display = "flex";
let imgDataSrc = null;
compressImage(canvas.toDataURL("image/png"), (file) => {
imgDataSrc = file;
uni.postMessage({
data: {
imageFile: imgDataSrc,
action: "getImageFile",
},
});
});
}
/**
* 压缩照片
*/
function compressImage(base64, callback) {
var targSize = 1024 * 1024; //1024KB
if (base64.length <= targSize) {
callback(base64);
// console.log("直接返回")
return;
}
var newImage = new Image();
newImage.src = base64;
// newImage.setAttribute("crossOrigin", 'Anonymous'); //url为外域时需要
newImage.onload = function () {
// let ratio = video.videoWidth / video.videoHeight;
var quality = 1; //压缩系数
// var canvas = document.createElement("canvas");
// var ctx = canvas.getContext("2d");
let ratio = video.videoWidth / video.videoHeight;
context.clearRect(0, 0, canvas.width, canvas.width / ratio);
// ctx.drawImage(video, 0, 0, canvas.width, canvas.width / ratio);
canvas.height = canvas.width / ratio;
context.drawImage(video, 0, 0, canvas.width, canvas.width / ratio);
var base64 = canvas.toDataURL("image/png", quality);
callback(base64); //必须通过回调函数返回,否则无法及时拿到该值
};
}
/**
* 打开闪光灯
*/
function onTorch() {
try {
var os = plus.os.name;
if ("iOS" == os) {
var device = plus.ios.invoke(
"AVCaptureDevice",
"defaultDeviceWithMediaType:",
"vide"
);
plus.ios.invoke(device, "lockForConfiguration:", null);
plus.ios.invoke(device, "setTorchMode:", 1);
plus.ios.invoke(device, "setFlashMode:", 1);
plus.ios.invoke(device, "unlockForConfiguration");
} else {
var main = plus.android.runtimeMainActivity();
var camera = main.getSystemService("camera");
var ids = plus.android.invoke(camera, "getCameraIdList");
for (var i = 0; i < ids.length; i++) {
var c = plus.android.invoke(
camera,
"getCameraCharacteristics",
ids[i]
);
var available = plus.android.invoke(
c,
"get",
plus.android.getAttribute(c, "FLASH_INFO_AVAILABLE")
);
var facing = plus.android.invoke(
c,
"get",
plus.android.getAttribute(c, "LENS_FACING")
);
if (
null != available &&
available &&
null != facing &&
1 == facing
) {
plus.android.invoke(camera, "setTorchMode", ids[i], true);
}
}
}
} catch (e) {
console.error(e, "error @onTorch!!");
}
}
/**
* 关闭闪光灯
*/
function offTorch() {
try {
var os = plus.os.name;
if ("iOS" == os) {
var device = plus.ios.invoke(
"AVCaptureDevice",
"defaultDeviceWithMediaType:",
"vide"
);
plus.ios.invoke(device, "lockForConfiguration:", null);
plus.ios.invoke(device, "setTorchMode:", 0);
plus.ios.invoke(device, "setFlashMode:", 0);
plus.ios.invoke(device, "unlockForConfiguration");
} else {
var main = plus.android.runtimeMainActivity();
var camera = main.getSystemService("camera");
var ids = plus.android.invoke(camera, "getCameraIdList");
for (var i = 0; i < ids.length; i++) {
var c = plus.android.invoke(
camera,
"getCameraCharacteristics",
ids[i]
);
var available = plus.android.invoke(
c,
"get",
plus.android.getAttribute(c, "FLASH_INFO_AVAILABLE")
);
var facing = plus.android.invoke(
c,
"get",
plus.android.getAttribute(c, "LENS_FACING")
);
if (
null != available &&
available &&
null != facing &&
1 == facing
) {
plus.android.invoke(camera, "setTorchMode", ids[i], false);
}
}
}
} catch (e) {
console.error("error @offTorch!!");
}
}
/**
* 上传图片回掉处理逻辑 start
* */
// 上传成功回调
function uploadCallback() {
var params = new URLSearchParams(window.location.search);
if (params.get("result")) {
let { code, data } = params.get("result");
if (code == 200) {
getPramas();
updateTitle();
setTimeout(() => {
loadingElement.style.display = "none";
}, 500);
}
}
}
let params = null;
function getQueryParams() {
const search = window.location.search.substring(1);
return JSON.parse(
'{"' +
decodeURIComponent(search)
.replace(/"/g, '\\"')
.replace(/&/g, '","')
.replace(/=/g, '":"') +
'"}'
);
}
// 通过侦听URL变化判断是否上传成功
timer = setInterval(() => {
const newParams = getQueryParams();
if (JSON.stringify(newParams) !== JSON.stringify(params)) {
console.log("Params changed:", newParams);
// 更新 params
params = newParams;
uploadCallback();
}
}, 1000); // 每秒检查一次
function clearTimer() {
console.log("清除计时器");
if (timer) {
clearInterval(timer);
timer = null;
}
}
// 侦听页面卸载 清除计数器
window.addEventListener("beforeunload", clearTimer);
window.addEventListener("unload", clearTimer);
/**
* 上传图片回掉处理逻辑 end
* */
</script>
</html>
src\hybrid\html\js\unisdk.js
! function (e, n) {
"object" == typeof exports && "undefined" != typeof module ? module.exports = n() : "function" == typeof define &&
define.amd ? define(n) : (e = e || self).uni = n()
}(this, (function () {
"use strict";
try {
var e = {};
Object.defineProperty(e, "passive", {
get: function () {
!0
}
}), window.addEventListener("test-passive", null, e)
} catch (e) { }
var n = Object.prototype.hasOwnProperty;
function i (e, i) {
return n.call(e, i)
}
var t = [];
function r () {
return window.__dcloud_weex_postMessage || window.__dcloud_weex_
}
var o = function (e, n) {
var i = {
options: {
timestamp: +new Date
},
name: e,
arg: n
};
if (r()) {
if ("postMessage" === e) {
var o = {
data: [n]
};
return window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessage(o) : window
.__dcloud_weex_.postMessage(JSON.stringify(o))
}
var a = {
type: "WEB_INVOKE_APPSERVICE",
args: {
data: i,
webviewIds: t
}
};
window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessageToService(a) : window
.__dcloud_weex_.postMessageToService(JSON.stringify(a))
}
if (!window.plus) return window.parent.postMessage({
type: "WEB_INVOKE_APPSERVICE",
data: i,
pageId: ""
}, "*");
if (0 === t.length) {
var d = plus.webview.currentWebview();
if (!d) throw new Error("plus.webview.currentWebview() is undefined");
var s = d.parent(),
w = "";
w = s ? s.id : d.id, t.push(w)
}
if (plus.webview.getWebviewById("__uniapp__service")) plus.webview.postMessageToUniNView({
type: "WEB_INVOKE_APPSERVICE",
args: {
data: i,
webviewIds: t
}
}, "__uniapp__service");
else {
var u = JSON.stringify(i);
plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat(
"WEB_INVOKE_APPSERVICE", '",').concat(u, ",").concat(JSON.stringify(t), ");"))
}
},
a = {
navigateTo: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
n = e.url;
o("navigateTo", {
url: encodeURI(n)
})
},
navigateBack: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
n = e.delta;
o("navigateBack", {
delta: parseInt(n) || 1
})
},
switchTab: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
n = e.url;
o("switchTab", {
url: encodeURI(n)
})
},
reLaunch: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
n = e.url;
o("reLaunch", {
url: encodeURI(n)
})
},
redirectTo: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
n = e.url;
o("redirectTo", {
url: encodeURI(n)
})
},
getEnv: function (e) {
r() ? e({
nvue: !0
}) : window.plus ? e({
plus: !0
}) : e({
h5: !0
})
},
postMessage: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {};
o("postMessage", e.data || {})
}
},
d = /uni-app/i.test(navigator.userAgent),
s = /Html5Plus/i.test(navigator.userAgent),
w = /complete|loaded|interactive/;
var u = window.my && navigator.userAgent.indexOf(["t", "n", "e", "i", "l", "C", "y", "a", "p", "i", "l",
"A"
].reverse().join("")) > -1;
var g = window.swan && window.swan.webView && /swan/i.test(navigator.userAgent);
var v = window.qq && window.qq.miniProgram && /QQ/i.test(navigator.userAgent) && /miniProgram/i.test(
navigator.userAgent);
var c = window.tt && window.tt.miniProgram && /toutiaomicroapp/i.test(navigator.userAgent);
var m = window.wx && window.wx.miniProgram && /micromessenger/i.test(navigator.userAgent) &&
/miniProgram/i.test(navigator.userAgent);
var p = window.qa && /quickapp/i.test(navigator.userAgent);
var f = window.ks && window.ks.miniProgram && /micromessenger/i.test(navigator.userAgent) &&
/miniProgram/i.test(navigator.userAgent);
var l = window.tt && window.tt.miniProgram && /Lark|Feishu/i.test(navigator.userAgent);
var _ = window.jd && window.jd.miniProgram && /micromessenger/i.test(navigator.userAgent) &&
/miniProgram/i.test(navigator.userAgent);
var E = window.xhs && window.xhs.miniProgram && /xhsminiapp/i.test(navigator.userAgent);
for (var h, P = function () {
window.UniAppJSBridge = !0, document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady", {
bubbles: !0,
cancelable: !0
}))
}, b = [function (e) {
if (d || s) return window.__dcloud_weex_postMessage || window.__dcloud_weex_ ? document
.addEventListener("DOMContentLoaded", e) : window.plus && w.test(document
.readyState) ? setTimeout(e, 0) : document.addEventListener("plusready", e), a
}, function (e) {
if (m) return window.WeixinJSBridge && window.WeixinJSBridge.invoke ? setTimeout(e, 0) :
document.addEventListener("WeixinJSBridgeReady", e), window.wx.miniProgram
}, function (e) {
if (v) return window.QQJSBridge && window.QQJSBridge.invoke ? setTimeout(e, 0) : document
.addEventListener("QQJSBridgeReady", e), window.qq.miniProgram
}, function (e) {
if (u) {
document.addEventListener("DOMContentLoaded", e);
var n = window.my;
return {
navigateTo: n.navigateTo,
navigateBack: n.navigateBack,
switchTab: n.switchTab,
reLaunch: n.reLaunch,
redirectTo: n.redirectTo,
postMessage: n.postMessage,
getEnv: n.getEnv
}
}
}, function (e) {
if (g) return document.addEventListener("DOMContentLoaded", e), window.swan.webView
}, function (e) {
if (c) return document.addEventListener("DOMContentLoaded", e), window.tt.miniProgram
}, function (e) {
if (p) {
window.QaJSBridge && window.QaJSBridge.invoke ? setTimeout(e, 0) : document
.addEventListener("QaJSBridgeReady", e);
var n = window.qa;
return {
navigateTo: n.navigateTo,
navigateBack: n.navigateBack,
switchTab: n.switchTab,
reLaunch: n.reLaunch,
redirectTo: n.redirectTo,
postMessage: n.postMessage,
getEnv: n.getEnv
}
}
}, function (e) {
if (f) return window.WeixinJSBridge && window.WeixinJSBridge.invoke ? setTimeout(e, 0) :
document.addEventListener("WeixinJSBridgeReady", e), window.ks.miniProgram
}, function (e) {
if (l) return document.addEventListener("DOMContentLoaded", e), window.tt.miniProgram
}, function (e) {
if (_) return window.JDJSBridgeReady && window.JDJSBridgeReady.invoke ? setTimeout(e, 0) :
document.addEventListener("JDJSBridgeReady", e), window.jd.miniProgram
}, function (e) {
if (E) return window.xhs.miniProgram
}, function (e) {
return document.addEventListener("DOMContentLoaded", e), a
}], y = 0; y < b.length && !(h = b[y](P)); y++);
h || (h = {});
var B = "undefined" != typeof uni ? uni : {};
if (!B.navigateTo)
for (var S in h) i(h, S) && (B[S] = h[S]);
return B.webView = h, B
}));
src\hybrid\html\js\jquery.js
! function (e, n) {
"object" == typeof exports && "undefined" != typeof module ? module.exports = n() : "function" == typeof define &&
define.amd ? define(n) : (e = e || self).uni = n()
}(this, (function () {
"use strict";
try {
var e = {};
Object.defineProperty(e, "passive", {
get: function () {
!0
}
}), window.addEventListener("test-passive", null, e)
} catch (e) { }
var n = Object.prototype.hasOwnProperty;
function i (e, i) {
return n.call(e, i)
}
var t = [];
function r () {
return window.__dcloud_weex_postMessage || window.__dcloud_weex_
}
var o = function (e, n) {
var i = {
options: {
timestamp: +new Date
},
name: e,
arg: n
};
if (r()) {
if ("postMessage" === e) {
var o = {
data: [n]
};
return window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessage(o) : window
.__dcloud_weex_.postMessage(JSON.stringify(o))
}
var a = {
type: "WEB_INVOKE_APPSERVICE",
args: {
data: i,
webviewIds: t
}
};
window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessageToService(a) : window
.__dcloud_weex_.postMessageToService(JSON.stringify(a))
}
if (!window.plus) return window.parent.postMessage({
type: "WEB_INVOKE_APPSERVICE",
data: i,
pageId: ""
}, "*");
if (0 === t.length) {
var d = plus.webview.currentWebview();
if (!d) throw new Error("plus.webview.currentWebview() is undefined");
var s = d.parent(),
w = "";
w = s ? s.id : d.id, t.push(w)
}
if (plus.webview.getWebviewById("__uniapp__service")) plus.webview.postMessageToUniNView({
type: "WEB_INVOKE_APPSERVICE",
args: {
data: i,
webviewIds: t
}
}, "__uniapp__service");
else {
var u = JSON.stringify(i);
plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat(
"WEB_INVOKE_APPSERVICE", '",').concat(u, ",").concat(JSON.stringify(t), ");"))
}
},
a = {
navigateTo: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
n = e.url;
o("navigateTo", {
url: encodeURI(n)
})
},
navigateBack: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
n = e.delta;
o("navigateBack", {
delta: parseInt(n) || 1
})
},
switchTab: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
n = e.url;
o("switchTab", {
url: encodeURI(n)
})
},
reLaunch: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
n = e.url;
o("reLaunch", {
url: encodeURI(n)
})
},
redirectTo: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
n = e.url;
o("redirectTo", {
url: encodeURI(n)
})
},
getEnv: function (e) {
r() ? e({
nvue: !0
}) : window.plus ? e({
plus: !0
}) : e({
h5: !0
})
},
postMessage: function () {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {};
o("postMessage", e.data || {})
}
},
d = /uni-app/i.test(navigator.userAgent),
s = /Html5Plus/i.test(navigator.userAgent),
w = /complete|loaded|interactive/;
var u = window.my && navigator.userAgent.indexOf(["t", "n", "e", "i", "l", "C", "y", "a", "p", "i", "l",
"A"
].reverse().join("")) > -1;
var g = window.swan && window.swan.webView && /swan/i.test(navigator.userAgent);
var v = window.qq && window.qq.miniProgram && /QQ/i.test(navigator.userAgent) && /miniProgram/i.test(
navigator.userAgent);
var c = window.tt && window.tt.miniProgram && /toutiaomicroapp/i.test(navigator.userAgent);
var m = window.wx && window.wx.miniProgram && /micromessenger/i.test(navigator.userAgent) &&
/miniProgram/i.test(navigator.userAgent);
var p = window.qa && /quickapp/i.test(navigator.userAgent);
var f = window.ks && window.ks.miniProgram && /micromessenger/i.test(navigator.userAgent) &&
/miniProgram/i.test(navigator.userAgent);
var l = window.tt && window.tt.miniProgram && /Lark|Feishu/i.test(navigator.userAgent);
var _ = window.jd && window.jd.miniProgram && /micromessenger/i.test(navigator.userAgent) &&
/miniProgram/i.test(navigator.userAgent);
var E = window.xhs && window.xhs.miniProgram && /xhsminiapp/i.test(navigator.userAgent);
for (var h, P = function () {
window.UniAppJSBridge = !0, document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady", {
bubbles: !0,
cancelable: !0
}))
}, b = [function (e) {
if (d || s) return window.__dcloud_weex_postMessage || window.__dcloud_weex_ ? document
.addEventListener("DOMContentLoaded", e) : window.plus && w.test(document
.readyState) ? setTimeout(e, 0) : document.addEventListener("plusready", e), a
}, function (e) {
if (m) return window.WeixinJSBridge && window.WeixinJSBridge.invoke ? setTimeout(e, 0) :
document.addEventListener("WeixinJSBridgeReady", e), window.wx.miniProgram
}, function (e) {
if (v) return window.QQJSBridge && window.QQJSBridge.invoke ? setTimeout(e, 0) : document
.addEventListener("QQJSBridgeReady", e), window.qq.miniProgram
}, function (e) {
if (u) {
document.addEventListener("DOMContentLoaded", e);
var n = window.my;
return {
navigateTo: n.navigateTo,
navigateBack: n.navigateBack,
switchTab: n.switchTab,
reLaunch: n.reLaunch,
redirectTo: n.redirectTo,
postMessage: n.postMessage,
getEnv: n.getEnv
}
}
}, function (e) {
if (g) return document.addEventListener("DOMContentLoaded", e), window.swan.webView
}, function (e) {
if (c) return document.addEventListener("DOMContentLoaded", e), window.tt.miniProgram
}, function (e) {
if (p) {
window.QaJSBridge && window.QaJSBridge.invoke ? setTimeout(e, 0) : document
.addEventListener("QaJSBridgeReady", e);
var n = window.qa;
return {
navigateTo: n.navigateTo,
navigateBack: n.navigateBack,
switchTab: n.switchTab,
reLaunch: n.reLaunch,
redirectTo: n.redirectTo,
postMessage: n.postMessage,
getEnv: n.getEnv
}
}
}, function (e) {
if (f) return window.WeixinJSBridge && window.WeixinJSBridge.invoke ? setTimeout(e, 0) :
document.addEventListener("WeixinJSBridgeReady", e), window.ks.miniProgram
}, function (e) {
if (l) return document.addEventListener("DOMContentLoaded", e), window.tt.miniProgram
}, function (e) {
if (_) return window.JDJSBridgeReady && window.JDJSBridgeReady.invoke ? setTimeout(e, 0) :
document.addEventListener("JDJSBridgeReady", e), window.jd.miniProgram
}, function (e) {
if (E) return window.xhs.miniProgram
}, function (e) {
return document.addEventListener("DOMContentLoaded", e), a
}], y = 0; y < b.length && !(h = b[y](P)); y++);
h || (h = {});
var B = "undefined" != typeof uni ? uni : {};
if (!B.navigateTo)
for (var S in h) i(h, S) && (B[S] = h[S]);
return B.webView = h, B
}));
src\hybrid\html\css\index.css
* {
margin: 0;
padding: 0;
}
body {
background-color: black;
}
.nav {
display: flex;
justify-content: space-between;
width: 100%;
height: 160px;
align-items: center;
}
.back-icon,
.switch-camera-icon {
font-size: 64px;
color: #fff;
}
.nav-title {
font-size: 38px;
color: #fff;
font-weight: 700;
flex: 1;
text-align: center;
}
.container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
}
canvas {
width: 100%;
}
video {
/* margin-top: 20px; */
/* width: 100%; */
/* height: calc(100% - 300px); */
/* height:200px; */
}
.btmBox {
position: fixed;
bottom: 0px;
height: 300px;
width: 100%;
background-color: black;
}
#albumBtn {
width: 100px;
height: 100px;
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 10%;
}
#finishBtn {
width: 160px;
height: 80px;
padding: 0;
background-color: #05b23b;
border: none;
border-radius: 4px;
color: #fff;
font-size: 32px;
line-height: 80px;
text-align: center;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 10%;
}
#capture {
font-size: 28px;
width: 180px;
height: 180px;
border-radius: 50%;
line-height: 50px;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.mb {}
.bg-img {
position: absolute;
top: 0;
width: 100%;
height: calc(100% - 300px);
z-index: 99;
}
.loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
z-index: 1000;
display: none; /* 默认隐藏 */
}
.loading-spinner {
border: 8px solid #f3f3f3;
border-top: 8px solid #3498db;
border-radius: 50%;
width: 80px;
height: 80px;
animation: spin 1s linear infinite;
}
.loading-text {
margin-top: 26px;
color: #fff;
font-size: 32px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
父组件中使用:
<template>
<view>
<web-view
ref="webviewRef"
id="webview"
:src="webViewUrl"
@message="handleMessage"></web-view>
</view>
</template>
<script setup>
import {
onMounted,
getCurrentInstance,
ref,
reactive,
nextTick,
computed,
watch,
} from "vue";
import { useUserStore, useAppStore } from "@/yaoli/store";
import api from "@/api/factory/appearance";
import { apiUpload } from "@/api/public";
import { netConfig } from "@/common/config";
import { pathToBase64, base64ToPath } from "image-tools";
import { onLoad } from "@dcloudio/uni-app";
const emits = defineEmits(["close", "success", "update:isShow"]);
const webviewRef = ref();
const props = defineProps({
id: {
type: Number,
default: 0,
},
maxUploadCount: {
type: Number,
},
titleList: {
type: Array,
default: () => [],
},
title: {
type: String,
},
});
const mainRef = ref();
const { proxy, ctx } = getCurrentInstance();
const { $util } = proxy;
const { $dict } = proxy;
const userStore = useUserStore();
const scrollRef = ref(); // 滚动条Ref
const appStore = useAppStore();
const parmas = appStore.getParmas();
// 自定义相机类型 可根据此参数更改相机样式与业务逻辑
const dataType = ref("");
let uploadCount = ref(null);
// 上传结果
let uploadResult = ref("");
// web-view地址
const webViewUrl = computed(() => {
return `/hybrid/html/index.html?type=${dataType}&title=${props.title}&titleList=${props.titleList}&index=${uploadCount.value}&topHight=${topHight}&result=${uploadResult.value}`;
});
const handleMessage = ({ detail }) => {
let action = detail?.data[0].action;
if (action == "getImageFile") {
saveImage(detail);
}
if (action == "getImagePath") {
let data = detail?.data[0] || {};
uploadImage(data.path);
}
if (action == "close") {
onClose();
}
};
// 上级路由信息
let lastRoute = {};
const onClose = () => {
emits("close", false);
};
let uploadImageData = {};
// base64格式图片转file格式并保存到本地
const saveImage = (detail) => {
let data = detail?.data[0] || {};
base64ToPath(data.imageFile)
.then((path) => {
// 上传图片
uploadImage(path);
})
.catch((err) => {
uni.$showMsg("上传失败,稍后重试");
uploadResult.value = JSON.stringify({
code: 500,
random: Math.random(), // 添加随机数引起url变化触发web-view侦听
});
});
};
const uploadImage = (filePath) => {
//本地图片上传默认配置
let defaultOption2 = {
url: netConfig.baseURL + "files/file/upload",
filePath: filePath, //图片路径
name: "file", // 服务器端接受文件的参数名
header: {},
success: (res) => {
console.log("上传成功", res);
let result = JSON.parse(res.data);
console.log(uploadCount.value, "uploadCount.value");
if (result.code == 200) {
uni.$showMsg("上传成功");
emits("success", res, uploadCount.value);
// setTimeout(() => {
// 判断是否限制拍照数量,超过数量退出
if (props.maxUploadCount) {
++uploadCount.value;
if (uploadCount.value >= props.maxUploadCount) {
onClose();
}
}
// }, 1000);
} else {
uni.$showMsg(result.message || "上传失败,稍后重试");
}
uploadResult.value = JSON.stringify({
...result,
random: Math.random(), // 添加随机数引起url变化触发web-view侦听
});
// 向web-view传参
// setTimeout(() => {
// let resultString = JSON.stringify({ uploadCount.value, data: result });
// // embed.evalJS(`uniMsg({uploadCount:${uploadCount.value},data:${result}})`);
// embed.evalJS(`uniMsg(${resultString})`);
// }, 200);
// // webviewRef.value.postMessage({result})
},
fail: (err) => {
console.error("上传失败:", err);
uni.$showMsg("上传失败,稍后重试");
uploadResult.value = JSON.stringify({
code: 500,
random: Math.random(), // 添加随机数引起url变化触发web-view侦听
});
},
};
uni.uploadFile(defaultOption2);
};
// 获取顶部安全区域
let app = uni.getSystemInfoSync();
let topHight = app.statusBarHeight;
console.log("顶部安全距离", topHight); //44
let wv = null;
let embed = null;
onLoad(() => {
setTimeout(() => {
// // 注册web-view
// wv = plus.webview.currentWebview();
// // embed = plus.webview.getWebviewById(plus.runtime.appid);
// embed = plus.webview.create(
// `/hybrid/html/index.html?type=${dataType}&title=${props.title}&titleList=${props.titleList}&index=${props.id}&topHight=${topHight}`,
// "",
// { top: "0px", bottom: "0px" }
// );
// embed.addEventListener("message", handleMessage);
// wv.append(embed);
// console.log(wv, "wv");
}, 200);
});
const show = () => {
// console.log("show");
// plus.webview.show(embed);
};
watch(
() => props.id,
() => {
uploadCount.value = props.id;
},
{ immediate: true }
);
defineExpose({
show,
});
</script>
<style lang="scss" scoped>
@import "./index.scss";
</style>
参考文档:
https://uniapp.dcloud.net.cn/component/web-view.html#web-view
https://www.html5plus.org/doc/zh_cn/nativeobj.html