bonus-material-app/src/pages/devicesSearch/ocrSearch.vue

996 lines
30 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="page-container" v-show="!showCamera">
<!-- 设备搜索页面 -->
<view v-if="!showCamera">
<view class="table-list-item">
<view class="scan-btn" @click="openCamera">
<text style="color: #FFF;">开始识别</text>
</view>
</view>
<view class="table-list-item">
<uni-row :gutter="24" style="display: flex; align-items: center">
<uni-col :span="5">
<text>设备编码</text>
</uni-col>
<uni-col :span="13">
<view>
<uni-easyinput v-model="queryCodeParams.maCode" placeholder="请输入内容" />
</view>
</uni-col>
<uni-col :span="6">
<view class="coding-btn" @click="getCode()">
<text style="color: #fe9a09;text-align: center;">编码检索</text>
</view>
</uni-col>
</uni-row>
<div v-if="optionList.length > 1" class="select-container">
<uni-data-select
v-model="queryCodeParams.maId"
:localdata="optionList"
@change="changeTag"
placeholder="请选择相关联编号"
class="data-select"
:searchable="false"
></uni-data-select>
</div>
</view>
<view class="table-list-item">
<uni-forms :model="codeData" label-width="100" :border="true">
<uni-forms-item label="设备类型:" name="maName">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.maName }}</text>
</uni-forms-item>
<uni-forms-item label="规格型号:" name="maModel">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.maModel }}</text>
</uni-forms-item>
<uni-forms-item label="设备编码:" name="maCode">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.maCode }}</text>
</uni-forms-item>
<uni-forms-item label="设备状态:" name="maStatus">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.maStatus }}</text>
</uni-forms-item>
<uni-forms-item label="本次检修时间:" name="thisCheckTime">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.thisCheckTime }}</text>
</uni-forms-item>
<uni-forms-item label="下次检修时间:" name="nextCheckTime">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.nextCheckTime }}</text>
</uni-forms-item>
<uni-forms-item label="出入库次数:" name="inOutNum">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.inOutNum }}</text>
</uni-forms-item>
<uni-forms-item label="初次入库:" name="inTime">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.inTime }}</text>
</uni-forms-item>
<uni-forms-item label="服务工程次数:" name="serviceNum">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.serviceNum }}</text>
</uni-forms-item>
<uni-forms-item label="检验次数:" name="serviceNum">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.serviceNum }}</text>
</uni-forms-item>
<uni-forms-item label="更换配件次数:" name="checkNum">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.checkNum }}</text>
</uni-forms-item>
<!-- <uni-forms-item label="检修员:" name="inspectionPerson">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.inspectionPerson }}</text>
</uni-forms-item> -->
<uni-forms-item label="报废:" name="scrapTime">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.scrapTime }}</text>
</uni-forms-item>
<uni-forms-item label="生产厂家:" name="maVender">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.maVender }}</text>
</uni-forms-item>
<uni-forms-item label="领料单位:" name="leaseUnit">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.leaseUnit }}</text>
</uni-forms-item>
<uni-forms-item label="领料工程:" name="leaseProject">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.leaseProject }}</text>
</uni-forms-item>
<uni-forms-item label="领料时间:" name="leaseTime">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.leaseTime }}</text>
</uni-forms-item>
<uni-forms-item label="退料单位:" name="backUnit">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.backUnit }}</text>
</uni-forms-item>
<uni-forms-item label="退料工程:" name="backProject">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.backProject }}</text>
</uni-forms-item>
<uni-forms-item label="退料时间:" name="backTime">
<text style="height: 100%;display: flex;align-items: center;">{{ codeData.backTime }}</text>
</uni-forms-item>
</uni-forms>
</view>
</view>
<!-- 隐藏的canvas用于图片处理 -->
<canvas
canvas-id="imageCanvas"
style="position: fixed; top: -9999px; left: -9999px; width: 1px; height: 1px;"
></canvas>
<div style="width: 100%;">
<div v-if="isOverToday && codeData.nextCheckTime">该工器具已临近下次检验时间,请及时退还至机具(物流)分公司!</div>
<ElectronicSeal v-else v-show="codeData.maId" :maCode="codeData?.maCode" :maId="codeData?.maId" :devType="2" />
</div>
</view>
<!-- 相机预览页面 -->
<view v-if="showCamera" class="camera-container">
<!-- 顶部提示 -->
<view class="top-tip" style="color: red; text-align: center;margin-top: 30px;">
<text class="tip-text">请将识别编码置于取景框内,完成扫描</text>
</view>
<!-- 取景框 -->
<view class="viewfinder-container">
</view>
<!-- 底部控制区 -->
<view class="bottom-controls">
<view class="control-btn" @click="closeCamera">
<text class="control-icon">✕</text>
</view>
<view class="photo-btn" @click="takePicture" :class="{ 'taking': isTaking, 'disabled': isFocusing }">
<view class="photo-btn-inner"></view>
</view>
<view class="control-btn" @click="openGallery">
<text class="control-icon">📷</text>
</view>
</view>
<!-- 加载提示 -->
<view v-if="isProcessing" class="loading-overlay">
<view class="loading-content">
<view class="loading-spinner"></view>
<text class="loading-text">正在识别中...</text>
</view>
</view>
</view>
<view class="custom-toast"
:class="{'show': isShow, 'top': position === 'top', 'bottom': position === 'bottom'}"
ref="toast">
{{ message }}
</view>
</template>
<script>
import {decryptWithSM4} from "../../utils/sm";
import {getDeviceListAPI} from "../../services/picking/outbound";
import ElectronicSeal from '@/components/ElectronicSeal/index.vue'
export default {
components: { ElectronicSeal },
data() {
return {
showCamera: false,
isTaking: false,
isProcessing: false,
cameraStarted: false,
queryParams: {},
queryCodeParams: {
maCode: "",
maId: ""
},
codeData: {},
optionList: [],
cameraReady: false,
deviceReadyTimeout: null,
pluginCheckRetries: 0,
maxRetries: 5,
// 聚焦相关状态
isFocusing: false,
focusSuccess: false,
showFocusIndicator: false,
focusIndicatorStyle: {},
autoFocusEnabled: true,
focusTimeout: null,
// 设备信息
systemInfo: null,
isOverToday: false,
isShow: false,
message: '',
position: 'bottom',
screenHeight: null,
screenWidth: null,
}
},
onBackPress(options) {
console.log("进了-----")
// 相机打开时处理返回事件
if (this.showCamera){
console.log('关闭相机...');
try {
this.stopCamera();
} catch (error) {
console.error('关闭相机出错:', error);
} finally {
this.showCamera = false;
this.cameraStarted = false;
this.resetFocusState();
}
return true; // 阻止默认返回行为
}
// 其他情况下允许默认返回
return false;
},
onShow() {
this.initializeCordova();
// 获取设备信息用于相机配置
this.getDeviceInfo();
},
onHide() {
this.cleanup();
},
beforeDestroy() {
this.cleanup();
},
methods: {
show(message, position = 'top', duration = 2000) {
this.message = message
this.position = position
this.isShow = true
setTimeout(() => {
this.isShow = false
}, duration)
},
handleIsOverToday() {
if (!this.codeData.nextCheckTime) {
this.isOverToday = true
} else {
const now = Date.now()
const nextCheckTimestamp = new Date(this.codeData.nextCheckTime).getTime()
this.isOverToday = nextCheckTimestamp <= now
console.log('🚀 ~ isOverToday ~ :', this.isOverToday)
}
},
// 获取设备信息
getDeviceInfo() {
try {
const systemInfo = uni.getSystemInfoSync();
console.log('设备信息:', systemInfo);
this.systemInfo = systemInfo;
// 获取屏幕高度和宽度
const screenHeight = systemInfo.screenHeight; // 屏幕高度1000
const screenWidth = systemInfo.screenWidth; // 屏幕宽度600
this.screenHeight = screenHeight;
this.screenWidth = screenWidth;
} catch (error) {
console.error('获取设备信息失败:', error);
}
},
// 初始化Cordova - 改进版本
initializeCordova() {
console.log('开始初始化Cordova...');
this.pluginCheckRetries = 0;
// 清除之前的超时
if (this.deviceReadyTimeout) {
clearTimeout(this.deviceReadyTimeout);
}
// 如果Cordova已经准备好直接检查插件
if (this.isCordovaReady()) {
this.onDeviceReady();
return;
}
// 监听deviceready事件
document.addEventListener('deviceready', this.onDeviceReady, false);
// 设置超时,防止无限等待
this.deviceReadyTimeout = setTimeout(() => {
console.warn('Cordova初始化超时尝试直接检查插件');
this.checkCameraPlugin();
}, 5000);
},
// 检查Cordova是否准备好
isCordovaReady() {
return !!(window.cordova && (
document.readyState === 'complete' ||
window.cordova.platformId
));
},
// 设备准备就绪
onDeviceReady() {
console.log('Cordova设备准备就绪');
// 清除超时
if (this.deviceReadyTimeout) {
clearTimeout(this.deviceReadyTimeout);
this.deviceReadyTimeout = null;
}
// 移除事件监听器,避免重复调用
document.removeEventListener('deviceready', this.onDeviceReady);
// 检查相机插件
this.checkCameraPlugin();
},
// 检查相机插件 - 改进版本
checkCameraPlugin() {
console.log(`检查相机插件... (尝试 ${this.pluginCheckRetries + 1}/${this.maxRetries})`);
// 按优先级检查插件可用性
const pluginAvailable = this.getCameraPlugin();
if (pluginAvailable) {
console.log('相机插件可用:', pluginAvailable);
this.cameraReady = true;
return;
}
this.pluginCheckRetries++;
if (this.pluginCheckRetries < this.maxRetries) {
// 递增延迟重试
const delay = Math.min(1000 * this.pluginCheckRetries, 5000);
console.log(`插件未就绪,${delay}ms后重试...`);
setTimeout(() => {
this.checkCameraPlugin();
}, delay);
} else {
console.error('相机插件检查失败,已达到最大重试次数');
this.cameraReady = false;
uni.showToast({
title: '相机插件初始化失败',
icon: 'none',
duration: 3000
});
}
},
// 获取相机插件引用 - 改进版本
getCameraPlugin() {
// 按照插件注册的实际路径检查
const possiblePaths = [
() => window.CameraPreview // 全局注册的路径
];
for (let getPlugin of possiblePaths) {
try {
const plugin = getPlugin();
if (plugin && typeof plugin.startCamera === 'function') {
console.log('找到相机插件:', plugin);
return plugin;
}
} catch (e) {
// 忽略访问错误,继续尝试下一个路径
}
}
return null;
},
// 打开相机 - 改进版本
async openCamera() {
console.log('尝试打开相机...');
this.codeData = {}
if (!this.cameraReady) {
// 再次尝试检查插件
this.checkCameraPlugin();
if (!this.cameraReady) {
uni.showModal({
title: '提示',
content: '相机插件未准备好,请确保应用已正确安装相机插件,或尝试重启应用',
showCancel: false
});
return;
}
}
try {
// 显示相机界面
this.showCamera = true;
// 等待UI更新
await this.$nextTick();
// 初始化相机
await this.initCamera();
} catch (error) {
console.error('打开相机失败:', error);
this.showCamera = false;
uni.showToast({
title: error.message || '打开相机失败',
icon: 'none',
duration: 2000
});
}
},
// 初始化相机 - 关键修改
// 初始化相机 - 调整坐标计算
async initCamera() {
return new Promise((resolve, reject) => {
const CameraPreview = this.getCameraPlugin();
if (!CameraPreview) {
reject(new Error('相机插件不可用'));
return;
}
// 获取整个相机包装器的尺寸
const cameraWrapper = document.querySelector('.viewfinder-container');
if (!cameraWrapper) {
reject(new Error('找不到相机容器'));
return;
}
const rect = cameraWrapper.getBoundingClientRect();
const options = {
x: rect.left,
y: rect.top,
width: rect.width,
height: rect.height,
camera: CameraPreview.CAMERA_DIRECTION?.BACK || 'back',
tapPhoto: false,
previewDrag: false,
toBack: false, // 确保相机在WebView上层
alpha: 1,
tapFocus: true,
disableExifHeaderStripping: false
};
console.log('相机配置:', options);
CameraPreview.startCamera(
options,
(result) => {
console.log('相机启动成功:', result);
this.cameraStarted = true;
resolve();
},
(error) => {
console.error('相机启动失败:', error);
this.cameraStarted = false;
reject(new Error(`相机启动失败: ${error}`));
}
);
});
},
// 点击聚焦
tapToFocus(x, y) {
const CameraPreview = this.getCameraPlugin();
if (CameraPreview && this.cameraStarted) {
CameraPreview.tapToFocus(
x,
y,
() => console.log('聚焦成功'),
(error) => console.error('聚焦失败', error)
);
}
},
// 关闭相机
async closeCamera() {
console.log('关闭相机...');
try {
await this.stopCamera();
} catch (error) {
console.error('关闭相机出错:', error);
} finally {
this.showCamera = false;
this.cameraStarted = false;
this.resetFocusState();
}
},
// 重置聚焦状态
resetFocusState() {
this.isFocusing = false;
this.focusSuccess = false;
this.showFocusIndicator = false;
if (this.focusTimeout) {
clearTimeout(this.focusTimeout);
this.focusTimeout = null;
}
},
// 停止相机
async stopCamera() {
if (!this.cameraStarted) {
return Promise.resolve();
}
const CameraPreview = this.getCameraPlugin();
if (!CameraPreview) {
return Promise.resolve();
}
return new Promise((resolve) => {
CameraPreview.stopCamera(
() => {
console.log('相机已停止');
this.cameraStarted = false;
resolve();
},
(error) => {
console.error('停止相机失败:', error);
this.cameraStarted = false;
resolve(); // 即使失败也继续
}
);
});
},
// 拍照 - 添加聚焦逻辑
async takePicture() {
if (!this.cameraStarted || this.isTaking || this.isFocusing) {
if (this.isFocusing) {
uni.showToast({
title: '正在聚焦,请稍候...',
icon: 'none',
duration: 1000
});
}
return;
}
console.log('开始拍照流程...');
this.isTaking = true;
try {
const CameraPreview = this.getCameraPlugin();
if (!CameraPreview) {
throw new Error('相机插件不可用');
}
// 拍照前先进行聚焦
console.log('拍照前聚焦...');
console.log('开始拍照...');
CameraPreview.takePicture({width:this.screenWidth, height:this.screenHeight, quality: 50}, async (base64PictureData) => {
console.log('拍照返回数据',base64PictureData);
await this.processImage(base64PictureData);
});
} catch (error) {
console.error('拍照过程出错:', error);
uni.showToast({
title: error.message || '拍照失败',
icon: 'none'
});
} finally {
this.isTaking = false;
}
},
// 修改后的方法
removeExifData(pureBase64) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
// 转换为dataURL然后提取纯base64部分
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
const cleanPureBase64 = dataUrl.split(',')[1]; // 去掉前缀
resolve(cleanPureBase64);
};
img.onerror = () => reject(new Error('图像加载失败'));
// 临时添加前缀用于加载
img.src = `data:image/jpeg;base64,${pureBase64}`;
});
},
// 处理图片
async processImage(imageData) {
this.isProcessing = true;
try {
// base64PictureData 是纯base64无前缀
const cleanBase64 = await this.removeExifData(imageData);
// cleanBase64 也是纯base64无前缀
console.log('清理后的纯base64:', cleanBase64);
console.log('开始OCR识别...');
uni.request({
url: '/material/app/ocr/getOcrCode',
method: 'POST',
data: {
image: cleanBase64,
jiju_type: '',
auth_lic: 'xIWDlaDVdijcBB4mjhGCPYk5Kvk8tHZJbUn+vW+ih15+MYx98e/PXyBmKL5gFcWMPznLgDA15QuSAnZQSLddwdy9HkZgtuQDEEZZ351Eyb1eiDUccUnyoSGIrNimbx5TooBNNPYqU4qJeFrPJXAqjBHzRrxoBxuR2CEGKQPgHC4='
},
header: {
"Content-Type": "application/json;charset=UTF-8"
},
success: async (res) => {
if (!res.data.code){
res=JSON.parse(decryptWithSM4(res.data))
} else {
res =res.data
}
const {data: resData} = res
if (resData.code===0){
if (resData.data.result) {
this.queryCodeParams.maCode = resData.data.result
await this.closeCamera();
await this.getCode();
await uni.showToast({
title: '识别成功',
icon: 'success'
});
} else {
console.log('识别失败!' + resData.data.msg)
this.show('识别失败!', 'bottom')
}
} else {
this.show('识别失败!', 'bottom')
}
},
fail: (err) => {
this.$refs.toast.show('请求失败!', 'bottom')
},
})
} catch (error) {
console.error('图片处理或OCR识别失败:', error);
uni.showToast({
title: '识别失败,请重试',
icon: 'none'
});
} finally {
this.isProcessing = false;
}
},
// 打开相册
openGallery() {
uni.chooseImage({
count: 1,
sourceType: ['album'],
success: (res) => {
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
const filePath = res.tempFilePaths[0];
// 统一使用XMLHttpRequest处理
const xhr = new XMLHttpRequest();
xhr.open('GET', filePath, true);
xhr.responseType = 'blob';
xhr.onload = () => {
if (xhr.status === 200) {
const blob = xhr.response;
const reader = new FileReader();
reader.onload = (e) => {
const base64 = e.target.result.split(',')[1];
this.convertImageToBase64(base64);
};
reader.readAsDataURL(blob);
}
};
xhr.send();
}
},
fail: (error) => {
console.error('选择图片失败:', error);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
},
// 将图片转换为base64
async convertImageToBase64(processedBase64) {
this.isProcessing = true;
try {
console.log('相册base64数据', processedBase64);
console.log('开始OCR识别...');
uni.request({
url: '/material/app/ocr/getOcrCode',
method: 'POST',
data: {
image: processedBase64,
jiju_type: '',
auth_lic: 'xIWDlaDVdijcBB4mjhGCPYk5Kvk8tHZJbUn+vW+ih15+MYx98e/PXyBmKL5gFcWMPznLgDA15QuSAnZQSLddwdy9HkZgtuQDEEZZ351Eyb1eiDUccUnyoSGIrNimbx5TooBNNPYqU4qJeFrPJXAqjBHzRrxoBxuR2CEGKQPgHC4='
},
header: {
"Content-Type": "application/json;charset=UTF-8"
},
success: async (res) => {
if (!res.data.code){
res=JSON.parse(decryptWithSM4(res.data))
} else {
res =res.data
}
console.log("res", res);
console.log("res.data",res.data)
const {data: resData} = res
console.log("resData",resData)
console.log("resData.data",resData.data)
console.log("resData.code",resData.code)
console.log("resData.data.result",resData.data.result)
if (resData.code===0){
if (resData.data.result) {
this.queryCodeParams.maCode = resData.data.result
await this.closeCamera();
await this.getCode();
await uni.showToast({
title: '识别成功',
icon: 'success'
});
} else {
this.show('识别失败!', 'bottom')
}
} else {
this.show('识别失败!', 'bottom')
}
},
fail: (err) => {
uni.showToast({
title: '请求失败:' + err.errMsg,
icon: 'none',
})
},
});
} catch (error) {
console.error('相册图片处理或OCR识别失败:', error);
this.show('识别失败!', 'bottom')
} finally {
this.isProcessing = false;
}
},
// 编码检索
getCode() {
if (!this.queryCodeParams.maCode.trim()) {
uni.showToast({
title: '请输入设备编码',
icon: 'none'
});
return;
}
try {
getDeviceListAPI({'maCode': this.queryCodeParams.maCode}).then(response => {
console.log("xxxxxxxxxxx", response)
if (response.code === 200 && response.data && response.data.length > 0) {
this.optionList = response.data.map(option => ({
value: option.maId,
text: option.maCode
}));
if (response.data.length === 1) {
this.codeData = response.data[0];
setTimeout(() => {
this.handleIsOverToday()
}, 500)
}
} else {
uni.showToast({
title: '未查询到该编号信息',
icon: 'none',
duration: 2000
})
}
}).catch(error => {
console.error("获取设备信息失败", error)
uni.hideLoading()
})
} catch (error) {
console.error('查询失败:', error);
uni.showToast({
title: '查询失败,请重试',
icon: 'none'
});
}
},
// 标签改变
async changeTag() {
if (!this.queryCodeParams.maId) return;
try {
const response = await getDeviceListAPI({'maId': this.queryCodeParams.maId})
if (response.data && response.data.length !== 0) {
this.codeData = response.data[0]
setTimeout(() => {
this.handleIsOverToday()
}, 500)
}
} catch (error) {
console.error("获取编号信息失败", error);
uni.showToast({
title: '获取信息失败',
icon: 'none'
});
}
},
// 清理资源
cleanup() {
console.log('清理资源...');
if (this.cameraStarted) {
const CameraPreview = this.getCameraPlugin();
if (CameraPreview) {
CameraPreview.stopCamera();
}
this.cameraStarted = false;
}
// 清除超时
if (this.deviceReadyTimeout) {
clearTimeout(this.deviceReadyTimeout);
this.deviceReadyTimeout = null;
}
if (this.focusTimeout) {
clearTimeout(this.focusTimeout);
this.focusTimeout = null;
}
// 移除事件监听
document.removeEventListener('deviceready', this.onDeviceReady);
// 停止相机
if (this.cameraStarted) {
this.stopCamera().catch(console.error);
}
// 重置聚焦状态
this.resetFocusState();
},
}
}
</script>
<style>
.custom-toast {
position: fixed;
left: 0;
right: 0;
padding: 12px;
background: rgba(0, 0, 0, 0.7);
color: white;
text-align: center;
z-index: 9999;
opacity: 0;
transition: opacity 0.3s;
}
.custom-toast.top { top: 0; }
.custom-toast.bottom { bottom: 0; }
.custom-toast.show { opacity: 1; }
.page-container {
display: flex;
height: auto;
flex-direction: column;
background-color: #f7f8fa;
padding: 24rpx;
}
.table-list-item {
background: #fff;
padding: 32rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
margin-bottom: 24rpx;
}
.scan-btn {
height: 88rpx;
background: #4b8eff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 6rpx 20rpx rgba(55, 132, 251, 0.2);
}
.coding-btn {
padding: 10rpx 0;
color: #fe9a09;
background-color: #fff7eb;
border: 1px solid #fe9a09;
border-radius: 12rpx;
text-align: center;
font-size: 28rpx;
font-weight: 600;
}
/* 控制按钮区域 - 需要单独设置可点击 */
.bottom-controls {
position: absolute;
bottom: 100rpx;
left: 0;
right: 0;
display: flex;
justify-content: space-around; /* 改为space-around使按钮分布更均匀 */
align-items: center;
width: 100%;
padding: 0 60rpx; /* 添加左右内边距 */
z-index: 3;
pointer-events: auto;
}
.control-btn {
width: 100rpx;
height: 100rpx;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border: 2rpx solid rgba(255, 255, 255, 0.3);
margin: 0 20rpx; /* 添加按钮间距 */
}
.control-icon {
color: #fff;
font-size: 40rpx;
font-weight: bold;
}
.photo-btn {
width: 140rpx;
height: 140rpx;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border: 6rpx solid rgba(255, 255, 255, 0.5);
transition: all 0.2s ease;
margin: 0 20rpx; /* 添加按钮间距 */
}
.photo-btn.taking {
transform: scale(0.9);
background-color: rgba(75, 142, 255, 0.8);
}
.photo-btn.disabled {
opacity: 0.5;
background-color: rgba(255, 255, 255, 0.1);
}
.photo-btn-inner {
width: 100rpx;
height: 100rpx;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 50%;
transition: all 0.2s ease;
}
.photo-btn.taking .photo-btn-inner {
background-color: #4b8eff;
}
.photo-btn.disabled .photo-btn-inner {
background-color: #ccc;
}
/* 加载层 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 100; /* 加载层在最高层 */
pointer-events: auto; /* 阻止底层交互 */
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
background-color: rgba(255, 255, 255, 0.9);
padding: 60rpx;
border-radius: 20rpx;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 6rpx solid #f3f3f3;
border-top: 6rpx solid #4b8eff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 30rpx;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loading-text {
color: #333;
font-size: 32rpx;
font-weight: bold;
}
.viewfinder-container {
position: absolute;
width: 100%;
height: 40%;
margin-top: 150px;
z-index: 10001; /* 提高取景框层级 */
}
</style>