| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 | <template>	<view class="camera-container">		<!-- 全屏相机预览 -->		<camera class="camera-preview" :resolution="'medium'" :device-position="devicePosition" :flash="flash"			:frame-size="frameSize" @stop="handleStop" @error="handleError" @initdone="handleInitDone">		</camera>		<!-- 左上角:闪光灯图标按钮 -->		<view class="top-left">			<button class="flash-btn" @click="switchFlash">				<text class="icon">{{ flash === 'torch' ? '💡' : '🔦' }}</text>			</button>		</view>		<!-- 右上角:图像质量设置 -->		<view class="top-right">			<view class="quality-setting">				<text class="setting-label">成像质量</text>				<radio-group class="quality-group" @change="takePhotoQualityChange">					<radio class="quality-radio" value="normal" :checked="quality === 'normal'">普通</radio>					<radio class="quality-radio" value="high" :checked="quality === 'high'">高清</radio>					<radio class="quality-radio" value="original" :checked="quality === 'original'">原图</radio>				</radio-group>			</view>		</view>		<!-- 底部中间:圆形拍照按钮 -->		<view class="bottom-center">			<button class="shoot-btn" @click="handleTakePhoto">				<view class="shoot-inner"></view>			</button>		</view>		<!-- 左下角:相册预览 -->		<view class="bottom-left">			<view class="album-preview" @click="handleScanCode">				<image class="preview-img" v-if="imageSrc" :src="imageSrc"></image>				<text class="preview-tip" v-else>浏览图片</text>			</view>		</view>		<!-- 下方:缩放控制(位置保持不变) -->		<view class="zoom-control">			<text class="setting-label">预览缩放</text>			<view class="zoom-container">				<slider class="zoom-slider" :disabled="maxZoom <= 1" :show-value="true" :min="1" :max="maxZoom"					:value="1" @change="zoomSliderChange" />			</view>		</view>	</view></template><script>	export default {		data() {			return {				devicePosition: "back",				flash: "off",				frameSize: "medium",				listener: null as CameraContextCameraFrameListener | null,				maxZoom: 0,				imageSrc: "",				quality: "normal",				timeout: 30,				compressed: false,				videoSrc: "",				startRecordStatus: false,				remain: 0,				intervalId: -1,				timeoutStr: '30',				recordId: 0,				senum: 0,				maxcount: 0,			}		},		onLoad(options) {			let recordId = options?.['id'] ?? ''			let senum = options?.['senum'] ?? ''			let maxcount = options?.['maxcount'] ?? ''			if(recordId!=''){				this.recordId = parseInt(recordId)			}			if(senum!=''){				this.senum = parseInt(senum)			}			if(maxcount!=''){				this.maxcount = parseInt(maxcount)			}		},		methods: {			handleScanCode() {				if(this.imageSrc!=""){					let params = "path="+this.imageSrc+"&recordId="+this.recordId+"&senum="+this.senum+"&maxcount="+this.maxcount					uni.navigateTo({						url: "/pages/work/record/camera-scan-code?"+params					})				}else{					uni.showToast({					  title: '没有拍照文件',					  icon: 'error', 					  duration: 2000 					});				}			},			switchDevicePosition() {				this.devicePosition = this.devicePosition == "back" ? "front" : "back"			},			switchFlash() {				this.flash = this.flash == "torch" ? "off" : "torch"			},			handleStop(e : UniCameraStopEvent) {				console.log("stop", e.detail);			},			handleError(e : UniCameraErrorEvent) {				console.log("error", e.detail);			},			handleInitDone(e : UniCameraInitDoneEvent) {				console.log("initdone", e.detail);				this.maxZoom = e.detail.maxZoom ?? 0			},			zoomSliderChange(event : UniSliderChangeEvent) {				const value = event.detail.value				const context = uni.createCameraContext();				context?.setZoom({					zoom: value,					success: (e : any) => {						console.log(e);					}				} as CameraContextSetZoomOptions)			},			handleTakePhoto() {				const context = uni.createCameraContext();				context?.takePhoto({					quality: this.quality,					selfieMirror: false,					success: (res : CameraContextTakePhotoResult) => {						console.log("res.tempImagePath", res.tempImagePath);						this.imageSrc = res.tempImagePath ?? ''					},					fail: (e : any) => {						console.log("take photo", e);					}				} as CameraContextTakePhotoOptions)			},			takePhotoQualityChange(event : UniRadioGroupChangeEvent) {				this.quality = event.detail.value				console.log("quality", this.quality);			},			setOnFrameListener() {				const context = uni.createCameraContext();				this.listener = context?.onCameraFrame((frame : CameraContextOnCameraFrame) => {					console.log("OnFrame :", frame);				})			},			startFrameListener() {				this.listener?.start({					success: (res : any) => {						console.log("startFrameListener success", res);					}				} as CameraContextCameraFrameListenerStartOptions)			},			stopFrameListener() {				this.listener?.stop({					success: (res : any) => {						console.log("stopFrameListener success", res);					}				} as CameraContextCameraFrameListenerStopOptions)			},			startRecord() {				const context = uni.createCameraContext();				let timeout = this.getTimeout()				this.timeout = timeout				context?.startRecord({					timeout: timeout,					selfieMirror: false,					timeoutCallback: (res : any) => {						console.log("timeoutCallback", res);						this.startRecordStatus = false						if (typeof res != "string") {							const result = res as CameraContextStartRecordTimeoutResult							this.videoSrc = result.tempVideoPath ?? ''						}						clearInterval(this.intervalId)					},					success: (res : any) => {						this.startRecordStatus = true						console.log("start record success", res);						this.remain = timeout						this.intervalId = setInterval(() => {							if (this.remain <= 0) {								clearInterval(this.intervalId)							} else {								this.remain--							}						}, 1000)					},					fail: (res : any) => {						console.log("start record fail", res);						this.startRecordStatus = false						this.remain = 0						clearInterval(this.intervalId)					}				} as CameraContextStartRecordOptions)			},			stopRecord() {				this.startRecordStatus = false				const context = uni.createCameraContext();				context?.stopRecord({					compressed: this.compressed,					success: (res : CameraContextStopRecordResult) => {						console.log("stop record success", res);						this.videoSrc = res.tempVideoPath ?? ''					},					fail: (res : any) => {						console.log("stop record fail", res);					}				} as CameraContextStopRecordOptions)				clearInterval(this.intervalId)				this.remain = 0			},			startRecordCompressChange(event : UniRadioGroupChangeEvent) {				this.compressed = parseInt(event.detail.value) == 1			},			timeoutInput(event : UniInputEvent) {				this.timeoutStr = event.detail.value			},			getTimeout() : number {				let value = parseInt(this.timeoutStr)				if (isNaN(value)) {					return 30				} else {					if (value < 1) {						return 1					} else if (value > 60 * 5) {						return 60 * 5					} else {						return value					}				}			}		}	}</script><style scope>	/* 基础容器 */	.camera-container {		width: 100%;		height: 100%;		position: relative;		overflow: hidden;		background-color: #000;	}	/* 相机预览 */	.camera-preview {		width: 100%;		height: 100%;		position: absolute;		top: 0;		left: 0;	}	/* 左上角:闪光灯按钮 */	.top-left {		position: absolute;		top: 40rpx;		left: 40rpx;		z-index: 10;	}	.flash-btn {		width: 100rpx;		height: 100rpx;		border-radius: 100rpx;		background-color: rgba(0, 0, 0, 0.4);		display: flex;		justify-content: center;		align-items: center;		padding: 0;		border: none;	}	.icon {		font-size: 48rpx;		color: #e0e0e0;		/* 银色 */	}	/* 右上角:图像质量设置 */	.top-right {		position: absolute;		top: 40rpx;		right: 40rpx;		z-index: 10;		background-color: rgba(0, 0, 0, 0.4);		padding: 20rpx 30rpx;		border-radius: 16rpx;	}	/* 底部中间:拍照按钮 */	.bottom-center {		position: absolute;		bottom: 240rpx;		left: 50%;		transform: translateX(-50%);		z-index: 10;	}	.shoot-btn {		width: 180rpx;		height: 180rpx;		border-radius: 180rpx;		background-color: #ffffff;		display: flex;		justify-content: center;		align-items: center;		padding: 0;		border: 8rpx solid rgba(255, 255, 255, 0.3);		box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0);	}	.shoot-inner {		width: 140rpx;		height: 140rpx;		border-radius: 140rpx;		background-color: #f0f0f0;	}	/* 左下角:相册预览 */	.bottom-left {		position: absolute;		bottom: 240rpx;		left: 40rpx;		z-index: 10;	}	.album-preview {		width: 130rpx;		height: 130rpx;		border: 4rpx solid #e0e0e0;		border-radius: 16rpx;		overflow: hidden;		display: flex;		justify-content: center;		align-items: center;		background-color: rgba(255, 255, 255, 0.1);	}	.preview-img {		width: 100%;		height: 100%;		object-fit: cover;	}	.preview-tip {		color: #e0e0e0;		/* 银色 */		font-size: 26rpx;	}	/* 下方:缩放控制 */	.zoom-control {		position: absolute;		bottom: 40rpx;		left: 0;		width: 100%;		padding: 0 40rpx;		box-sizing: border-box;		z-index: 10;	}	.setting-label {		display: block;		color: #e0e0e0;		/* 银色 */		font-size: 28rpx;		margin-bottom: 16rpx;		font-weight: 500;	}	/* 质量选择样式 */	.quality-group {		display: flex;		gap: 10;	}	.quality-radio {		color: #e0e0e0;		/* 银色 */		font-size: 26rpx;		display: flex;		align-items: center;		gap: 10;	}	/* 缩放控制样式 */	.zoom-container {		width: 100%;		padding: 10rpx 0;	}	.zoom-slider {		width: 100%;		height: 4px;	}</style>
 |