feat: 以图搜图集成拍照功能,支持直接调起摄像头搜图
This commit is contained in:
@ -33,6 +33,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
|
|
||||||
|
<!-- 拍照按钮 -->
|
||||||
|
<el-button
|
||||||
|
v-if="!previewUrl"
|
||||||
|
type="primary"
|
||||||
|
class="camera-btn"
|
||||||
|
@click="openCamera"
|
||||||
|
>
|
||||||
|
<el-icon><VideoCamera /></el-icon>
|
||||||
|
调起摄像头拍照
|
||||||
|
</el-button>
|
||||||
|
|
||||||
<div v-if="searching" class="loading-tip">
|
<div v-if="searching" class="loading-tip">
|
||||||
<el-icon class="is-loading"><Loading /></el-icon>
|
<el-icon class="is-loading"><Loading /></el-icon>
|
||||||
<span>正在识别图片并检索...</span>
|
<span>正在识别图片并检索...</span>
|
||||||
@ -94,15 +105,33 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="handleClose">关闭</el-button>
|
<el-button @click="handleClose">关闭</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 拍照弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="cameraVisible"
|
||||||
|
title="拍照"
|
||||||
|
width="95%"
|
||||||
|
style="max-width: 480px; height: 80vh; padding: 0;"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="closeCamera"
|
||||||
|
>
|
||||||
|
<WebRtcCamera
|
||||||
|
@cancel="closeCamera"
|
||||||
|
@photo-submit="handleCameraSubmit"
|
||||||
|
/>
|
||||||
|
</el-dialog>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { Camera, Loading, Picture, WarningFilled } from '@element-plus/icons-vue'
|
import { Camera, Loading, Picture, WarningFilled, VideoCamera } from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { imageSearch, type ImageSearchItem } from '@/api/common/upload'
|
import { imageSearch, type ImageSearchItem } from '@/api/common/upload'
|
||||||
|
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@ -126,6 +155,9 @@ const searching = ref(false)
|
|||||||
const searched = ref(false)
|
const searched = ref(false)
|
||||||
const results = ref<ImageSearchItem[]>([])
|
const results = ref<ImageSearchItem[]>([])
|
||||||
|
|
||||||
|
// 拍照相关
|
||||||
|
const cameraVisible = ref(false)
|
||||||
|
|
||||||
watch(() => props.modelValue, (val) => {
|
watch(() => props.modelValue, (val) => {
|
||||||
visible.value = val
|
visible.value = val
|
||||||
if (!val) {
|
if (!val) {
|
||||||
@ -137,6 +169,27 @@ watch(visible, (val) => {
|
|||||||
emit('update:modelValue', val)
|
emit('update:modelValue', val)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 拍照相关方法
|
||||||
|
const openCamera = () => {
|
||||||
|
cameraVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeCamera = () => {
|
||||||
|
cameraVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCameraSubmit = (file: File) => {
|
||||||
|
// 关闭拍照弹窗
|
||||||
|
closeCamera()
|
||||||
|
|
||||||
|
// 生成预览
|
||||||
|
currentFile.value = file
|
||||||
|
previewUrl.value = URL.createObjectURL(file)
|
||||||
|
|
||||||
|
// 立即触发搜图
|
||||||
|
doSearch(file)
|
||||||
|
}
|
||||||
|
|
||||||
const handleFileChange = (uploadFile: any) => {
|
const handleFileChange = (uploadFile: any) => {
|
||||||
const file = uploadFile.raw
|
const file = uploadFile.raw
|
||||||
if (!file) return
|
if (!file) return
|
||||||
@ -179,6 +232,9 @@ const doSearch = async (file: File) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const clearImage = () => {
|
const clearImage = () => {
|
||||||
|
if (previewUrl.value) {
|
||||||
|
URL.revokeObjectURL(previewUrl.value)
|
||||||
|
}
|
||||||
previewUrl.value = ''
|
previewUrl.value = ''
|
||||||
currentFile.value = null
|
currentFile.value = null
|
||||||
results.value = []
|
results.value = []
|
||||||
@ -188,7 +244,6 @@ const clearImage = () => {
|
|||||||
|
|
||||||
const fullImageUrl = (path: string) => {
|
const fullImageUrl = (path: string) => {
|
||||||
if (!path) return '';
|
if (!path) return '';
|
||||||
// 直接原样返回,完全信任后端传过来的 image_url
|
|
||||||
return path.startsWith('http') ? path : path;
|
return path.startsWith('http') ? path : path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +274,9 @@ const handleClose = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resetState = () => {
|
const resetState = () => {
|
||||||
|
if (previewUrl.value) {
|
||||||
|
URL.revokeObjectURL(previewUrl.value)
|
||||||
|
}
|
||||||
previewUrl.value = ''
|
previewUrl.value = ''
|
||||||
currentFile.value = null
|
currentFile.value = null
|
||||||
searching.value = false
|
searching.value = false
|
||||||
@ -234,6 +292,12 @@ const resetState = () => {
|
|||||||
min-height: 380px;
|
min-height: 380px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 拍照按钮 */
|
||||||
|
.camera-btn {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── 左侧上传区 ── */
|
/* ── 左侧上传区 ── */
|
||||||
.upload-section {
|
.upload-section {
|
||||||
flex: 0 0 220px;
|
flex: 0 0 220px;
|
||||||
|
|||||||
Reference in New Issue
Block a user