feat: 以图搜图集成拍照功能,支持直接调起摄像头搜图

This commit is contained in:
DXC
2026-06-05 15:47:50 +08:00
parent 1def8c7747
commit 93b9846fc6

View File

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