SmartParks_uniapp/pages/sys/workbench/camera.vue

815 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="camera-container">
<!-- 相机预览区域 -->
<view class="camera-preview">
<!-- #ifdef H5 -->
<view class="camera-placeholder">
<text class="placeholder-text">H5端需要特殊配置才能使用相机</text>
</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS || MP -->
<camera
:device-position="devicePosition"
:flash="flashMode"
@error="cameraError"
class="camera"
ref="camera"
></camera>
<!-- #endif -->
<!-- 相机控制区域 -->
<view class="camera-controls">
<!-- 顶部控制栏 -->
<view class="top-controls">
<view class="control-btn" @click="toggleFlash">
<image
:src="flashMode === 'off' ? '/static/ic_flash_off.png' : '/static/ic_flash_on.png'"
class="control-icon"
></image>
</view>
<view class="control-btn" @click="switchCamera">
<image src="/static/ic_camera_switch.png" class="control-icon"></image>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-controls">
<!-- 相册入口 -->
<view class="album-entry" @click="openAlbum">
<image v-if="latestMedia" :src="latestMedia" class="album-thumb"></image>
<view v-else class="album-placeholder"></view>
</view>
<!-- 拍摄按钮 -->
<view class="capture-area">
<view
class="capture-btn"
:class="{ recording: isRecording }"
@touchstart="startCapture"
@touchend="stopCapture"
>
<view class="capture-inner"></view>
</view>
</view>
<!-- 模式切换 -->
<view class="mode-toggle">
<view
class="mode-btn"
:class="{ active: captureMode === 'photo' }"
@click="switchMode('photo')"
>
拍照
</view>
<view
class="mode-btn"
:class="{ active: captureMode === 'video' }"
@click="switchMode('video')"
>
录像
</view>
</view>
</view>
</view>
<!-- 录制时间显示 -->
<view v-if="isRecording" class="recording-timer">
{{ formatTime(recordTime) }}
</view>
</view>
<!-- 结果预览区域 -->
<view v-if="capturedMedia" class="preview-container">
<image v-if="captureMode === 'photo'" :src="capturedMedia" class="preview-image"></image>
<video v-else :src="capturedMedia" class="preview-video" autoplay controls></video>
<view class="preview-actions">
<view class="action-btn cancel-btn" @click="cancelPreview">取消</view>
<view class="action-btn confirm-btn" @click="confirmMedia">使用</view>
</view>
</view>
<!-- 权限提示 -->
<view v-if="showPermissionTip" class="permission-tip">
<view class="tip-content">
<text class="tip-text">需要相机权限才能使用拍照功能</text>
<view class="tip-buttons">
<button class="tip-btn" @click="cancelPermission">取消</button>
<button class="tip-btn confirm" @click="requestPermission">去设置</button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
devicePosition: 'back', // 'front' or 'back'
flashMode: 'off', // 'off', 'on', 'auto'
captureMode: 'photo', // 'photo' or 'video'
isRecording: false,
recordTime: 0,
recordTimer: null,
capturedMedia: '',
latestMedia: '',
hasCameraPermission: false,
showPermissionTip: false,
cameraContext: null
}
},
onLoad() {
// 页面加载时检查权限
this.checkPermission()
},
onShow() {
// 页面显示时重置状态
this.resetState()
},
methods: {
// 检查权限
checkPermission() {
// #ifdef APP-PLUS
this.checkAppPermission()
// #endif
// #ifdef MP-WEIXIN
this.checkMPPermission()
// #endif
// #ifdef H5
// H5默认可以使用
this.hasCameraPermission = true
// #endif
},
// 检查App权限
checkAppPermission() {
// #ifdef APP-PLUS
// 先检查是否已有权限
// #ifdef APP-PLUS && ANDROID
// Android平台检查权限
var Context = plus.android.importClass("android.content.Context");
var main = plus.android.runtimeMainActivity();
var PackageManager = plus.android.importClass("android.content.pm.PackageManager");
var permission = "android.permission.CAMERA";
var result = main.checkSelfPermission(permission);
if (result === PackageManager.PERMISSION_GRANTED) {
this.hasCameraPermission = true;
} else {
// 请求权限
plus.android.requestPermissions(
[permission],
(resultObj) => {
let denied = false;
for (let i = 0; i < resultObj.denied.length; i++) {
denied = true;
break;
}
this.hasCameraPermission = !denied;
if (denied) {
this.showPermissionTip = true;
}
},
(error) => {
console.error('权限请求失败:', error);
this.hasCameraPermission = false;
this.showPermissionTip = true;
}
);
}
// #endif
// #ifdef APP-PLUS && IOS
// iOS平台默认认为有权限实际使用时会弹出授权框
this.hasCameraPermission = true;
// #endif
// #endif
},
// 检查小程序权限
checkMPPermission() {
// #ifdef MP-WEIXIN
uni.getSetting({
success: (res) => {
if (res.authSetting['scope.camera']) {
this.hasCameraPermission = true;
} else {
this.requestMPPermission();
}
},
fail: () => {
this.requestMPPermission();
}
});
// #endif
},
// 请求小程序权限
requestMPPermission() {
// #ifdef MP-WEIXIN
uni.authorize({
scope: 'scope.camera',
success: () => {
this.hasCameraPermission = true;
},
fail: () => {
this.showPermissionTip = true;
}
});
// #endif
},
// 请求权限
requestPermission() {
this.showPermissionTip = false;
// #ifdef APP-PLUS
if (plus.os.name == 'Android') {
var Intent = plus.android.importClass('android.content.Intent');
var Settings = plus.android.importClass('android.provider.Settings');
var main = plus.android.runtimeMainActivity();
var intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = plus.android.invoke('android.net.Uri', 'fromParts', 'package', main.getPackageName(), null);
plus.android.invoke(intent, 'setData', uri);
main.startActivity(intent);
} else if (plus.os.name == 'iOS') {
plus.runtime.openURL('app-settings:');
}
// #endif
// #ifdef MP-WEIXIN
uni.openSetting({
success: (setting) => {
if (setting.authSetting['scope.camera']) {
this.hasCameraPermission = true;
}
}
});
// #endif
},
// 取消权限请求
cancelPermission() {
this.showPermissionTip = false;
},
// 重置状态
resetState() {
this.capturedMedia = '';
this.isRecording = false;
if (this.recordTimer) {
clearInterval(this.recordTimer);
this.recordTimer = null;
}
this.recordTime = 0;
},
// 切换闪光灯
toggleFlash() {
if (!this.hasCameraPermission) {
this.showNoPermissionTip();
return;
}
this.flashMode = this.flashMode === 'off' ? 'on' : 'off';
},
// 切换前后摄像头
switchCamera() {
if (!this.hasCameraPermission) {
this.showNoPermissionTip();
return;
}
this.devicePosition = this.devicePosition === 'back' ? 'front' : 'back';
},
// 切换拍摄模式
switchMode(mode) {
if (!this.hasCameraPermission) {
this.showNoPermissionTip();
return;
}
this.captureMode = mode;
},
// 开始拍摄
startCapture() {
if (!this.hasCameraPermission) {
this.showNoPermissionTip();
return;
}
if (this.captureMode === 'photo') {
this.takePhoto();
} else {
this.startRecord();
}
},
// 停止拍摄
stopCapture() {
if (!this.hasCameraPermission) {
this.showNoPermissionTip();
return;
}
if (this.captureMode === 'video' && this.isRecording) {
this.stopRecord();
}
},
// 拍照
takePhoto() {
if (!this.hasCameraPermission) {
this.showNoPermissionTip();
return;
}
// #ifdef APP-PLUS
const ctx = uni.createCameraContext();
ctx.takePhoto({
quality: 'high',
success: (res) => {
this.capturedMedia = res.tempImagePath;
this.latestMedia = res.tempImagePath;
},
fail: (err) => {
console.error('拍照失败:', err);
// 检查是否是权限问题
if (err.errMsg && (err.errMsg.includes('permission') || err.errMsg.includes('权限'))) {
this.hasCameraPermission = false;
this.showPermissionTip = true;
} else {
uni.showToast({
title: '拍照失败',
icon: 'none'
});
}
}
});
// #endif
// #ifdef MP-WEIXIN
const ctx = uni.createCameraContext();
ctx.takePhoto({
quality: 'high',
success: (res) => {
this.capturedMedia = res.tempImagePath;
this.latestMedia = res.tempImagePath;
},
fail: (err) => {
console.error('拍照失败:', err);
// 检查是否是权限问题
if (err.errMsg && (err.errMsg.includes('permission') || err.errMsg.includes('权限'))) {
this.hasCameraPermission = false;
this.showPermissionTip = true;
} else {
uni.showToast({
title: '拍照失败',
icon: 'none'
});
}
}
});
// #endif
},
// 开始录像
startRecord() {
if (!this.hasCameraPermission) {
this.showNoPermissionTip();
return;
}
// #ifdef APP-PLUS
const ctx = uni.createCameraContext();
ctx.startRecord({
success: () => {
this.isRecording = true;
this.recordTime = 0;
this.recordTimer = setInterval(() => {
this.recordTime++;
}, 1000);
},
fail: (err) => {
console.error('开始录像失败:', err);
// 检查是否是权限问题
if (err.errMsg && (err.errMsg.includes('permission') || err.errMsg.includes('权限'))) {
this.hasCameraPermission = false;
this.showPermissionTip = true;
} else {
uni.showToast({
title: '开始录像失败',
icon: 'none'
});
}
}
});
// #endif
// #ifdef MP-WEIXIN
const ctx = uni.createCameraContext();
ctx.startRecord({
success: () => {
this.isRecording = true;
this.recordTime = 0;
this.recordTimer = setInterval(() => {
this.recordTime++;
}, 1000);
},
fail: (err) => {
console.error('开始录像失败:', err);
// 检查是否是权限问题
if (err.errMsg && (err.errMsg.includes('permission') || err.errMsg.includes('权限'))) {
this.hasCameraPermission = false;
this.showPermissionTip = true;
} else {
uni.showToast({
title: '开始录像失败',
icon: 'none'
});
}
}
});
// #endif
},
// 停止录像
stopRecord() {
if (!this.hasCameraPermission) {
this.showNoPermissionTip();
return;
}
// #ifdef APP-PLUS
const ctx = uni.createCameraContext();
ctx.stopRecord({
success: (res) => {
clearInterval(this.recordTimer);
this.recordTimer = null;
this.isRecording = false;
this.capturedMedia = res.tempVideoPath;
this.latestMedia = res.tempVideoPath;
},
fail: (err) => {
console.error('停止录像失败:', err);
uni.showToast({
title: '停止录像失败',
icon: 'none'
});
}
});
// #endif
// #ifdef MP-WEIXIN
const ctx = uni.createCameraContext();
ctx.stopRecord({
success: (res) => {
clearInterval(this.recordTimer);
this.recordTimer = null;
this.isRecording = false;
this.capturedMedia = res.tempVideoPath;
this.latestMedia = res.tempVideoPath;
},
fail: (err) => {
console.error('停止录像失败:', err);
uni.showToast({
title: '停止录像失败',
icon: 'none'
});
}
});
// #endif
},
// 显示无权限提示
showNoPermissionTip() {
uni.showToast({
title: '无相机权限,请先授权',
icon: 'none'
});
this.showPermissionTip = true;
},
// 格式化时间
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
},
// 打开相册
openAlbum() {
uni.chooseImage({
count: 1,
success: (res) => {
this.capturedMedia = res.tempFilePaths[0];
this.captureMode = 'photo';
},
fail: (err) => {
console.error('选择图片失败:', err);
}
});
},
// 取消预览
cancelPreview() {
this.capturedMedia = '';
},
// 确认使用媒体
confirmMedia() {
// 将媒体文件传递回上一个页面
const pages = getCurrentPages();
if (pages.length > 1) {
const prevPage = pages[pages.length - 2];
if (prevPage && prevPage.$vm) {
if (this.captureMode === 'photo') {
if (prevPage.$vm.onPhotoTaken) {
prevPage.$vm.onPhotoTaken(this.capturedMedia);
}
} else {
if (prevPage.$vm.onVideoTaken) {
prevPage.$vm.onVideoTaken(this.capturedMedia);
}
}
}
}
// 返回上一页
uni.navigateBack();
},
// 相机错误处理
cameraError(e) {
console.error('相机错误:', e);
uni.showToast({
title: '相机初始化失败',
icon: 'none'
});
}
},
beforeDestroy() {
// 清理定时器
if (this.recordTimer) {
clearInterval(this.recordTimer);
this.recordTimer = null;
}
}
}
</script>
<style scoped>
.camera-container {
width: 100vw;
height: 100vh;
position: relative;
background: #000;
}
.camera-preview {
width: 100%;
height: 100%;
position: relative;
}
.camera {
width: 100%;
height: 100%;
}
.camera-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 32rpx;
text-align: center;
}
.placeholder-text {
padding: 40rpx;
}
.camera-controls {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.camera-controls > view {
pointer-events: auto;
}
.top-controls {
display: flex;
justify-content: space-between;
padding: 40rpx;
}
.control-btn {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
}
.control-icon {
width: 40rpx;
height: 40rpx;
}
.bottom-controls {
position: absolute;
bottom: 40rpx;
left: 0;
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
}
.album-entry {
width: 80rpx;
height: 80rpx;
border-radius: 16rpx;
overflow: hidden;
background: rgba(255, 255, 255, 0.2);
}
.album-thumb {
width: 100%;
height: 100%;
object-fit: cover;
}
.album-placeholder {
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.1);
}
.capture-area {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 8rpx solid rgba(255, 255, 255, 0.3);
display: flex;
align-items: center;
justify-content: center;
}
.capture-btn {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.capture-btn.recording {
background: #ff4d4f;
border-color: #ff4d4f;
transform: scale(1.1);
}
.capture-inner {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #fff;
}
.mode-toggle {
display: flex;
background: rgba(0, 0, 0, 0.3);
border-radius: 50rpx;
overflow: hidden;
}
.mode-btn {
padding: 16rpx 32rpx;
font-size: 28rpx;
color: #fff;
}
.mode-btn.active {
background: rgba(255, 255, 255, 0.3);
}
.recording-timer {
position: absolute;
top: 40rpx;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.5);
color: #fff;
padding: 10rpx 20rpx;
border-radius: 30rpx;
font-size: 28rpx;
}
.preview-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
z-index: 100;
}
.preview-image,
.preview-video {
width: 100%;
height: 80%;
object-fit: contain;
}
.preview-actions {
display: flex;
justify-content: space-around;
padding: 40rpx;
}
.action-btn {
padding: 20rpx 60rpx;
border-radius: 50rpx;
font-size: 32rpx;
color: #fff;
}
.cancel-btn {
background: #999;
}
.confirm-btn {
background: #007aff;
}
.permission-tip {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 200;
}
.tip-content {
width: 80%;
background: #fff;
border-radius: 20rpx;
padding: 40rpx;
text-align: center;
}
.tip-text {
font-size: 32rpx;
color: #333;
margin-bottom: 40rpx;
display: block;
}
.tip-buttons {
display: flex;
justify-content: space-between;
}
.tip-btn {
flex: 1;
padding: 20rpx;
border-radius: 10rpx;
font-size: 28rpx;
margin: 0 20rpx;
}
.tip-btn.confirm {
background: #007aff;
color: #fff;
}
</style>