815 lines
19 KiB
Vue
815 lines
19 KiB
Vue
<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> |