Files
SmartParks_uniapp/pages/sys/workbench/camera.vue

815 lines
19 KiB
Vue
Raw Normal View History

<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>