Browse Source

摄像头拍照

master
xuhuajiao 1 month ago
parent
commit
f19d1ccbe9
  1. 18
      src/ssl/cert.csr
  2. 22
      src/ssl/cert.pem
  3. 28
      src/ssl/key.pem
  4. 123
      src/views/faceRecognition/module/camera.vue
  5. 63
      src/views/faceRecognition/personInfoManage.vue
  6. 73
      src/views/faceRecognition/personRegister.vue
  7. 6
      vue.config.js

18
src/ssl/cert.csr

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAkNOMQ4wDAYDVQQIDAVIdWJlaTEOMAwG
A1UEBwwFV3VoYW4xETAPBgNVBAoMCE15VnVlRGV2MQwwCgYDVQQLDANEZXYxHzAd
BgNVBAMMFmh0dHBzOi8vMTkyLjE2OC45OS4xMTgxHzAdBgkqhkiG9w0BCQEWEDY2
NDEzMTU4NkBxcS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC
JGkxYiVn83vjgfNMNWoVXhprZnpTD+4EAVnXI8L9c+ij+N+IFu6Q1wgY84y6BKTU
8RBVHpxBDp3MNe7POkJ3BVBf+r3fyqkpmoJQV6ZK8bokrxUnXyUsMgXgmshkcqAX
IcRB3QTrHwMDWHi5yv5VIw/jvEYycNclm9fMr78ra4UVR3rGv1gGnDag6+865uAk
YSGYjGrX1cXdfeha8FIYNp930Zn+hiIOfHTAGPwMLbgFARUj10F2bGojfc4vOU4p
T4nEb9evKvIXY0igjNrTXiLoXGnw7HiiX3rjVrEqF5/UiZrotLUKAyXie9uGQi/u
EoUmQB1a+0wI7PqDBsyVAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAO4xQJy/u
BV8pXI9NP3YOWGgIpswGJYmxId8AzmfxHoFJC7lMlEbXWdwXcZyCp9DKv+g6JoZA
lB+zSQGRvZXJTflvaZRCgRox5ChMIc0dWKb6amXwnSNLUJ1NYA2bErunKGiWnjQR
xFoE0VpYBafTDbPDNDP/W4871u+TYZ8kWQkWnE1ncEEGZ9EfzObwYVoMBdiTR+wI
CK8D7ixPC4mFhvtx4u5+RYtJGXfckhZEc7pPHK/YRkFCJHd4W1BnHpP6/mVlVdX/
i3sH5p8pahmgwLzozU0dY0E0WsaXZ0aJY26Qg3Nrj23rutG8Ekzqd2WBRZopsi6p
CXWurQLVXlLX+g==
-----END CERTIFICATE REQUEST-----

22
src/ssl/cert.pem

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDqTCCApECFBXLkKuse7kIi5EkiIaa5BNwiiDeMA0GCSqGSIb3DQEBCwUAMIGQ
MQswCQYDVQQGEwJDTjEOMAwGA1UECAwFSHViZWkxDjAMBgNVBAcMBVd1aGFuMREw
DwYDVQQKDAhNeVZ1ZURldjEMMAoGA1UECwwDRGV2MR8wHQYDVQQDDBZodHRwczov
LzE5Mi4xNjguOTkuMTE4MR8wHQYJKoZIhvcNAQkBFhA2NjQxMzE1ODZAcXEuY29t
MB4XDTI1MDkyMzA0MTkwOVoXDTI2MDkyMzA0MTkwOVowgZAxCzAJBgNVBAYTAkNO
MQ4wDAYDVQQIDAVIdWJlaTEOMAwGA1UEBwwFV3VoYW4xETAPBgNVBAoMCE15VnVl
RGV2MQwwCgYDVQQLDANEZXYxHzAdBgNVBAMMFmh0dHBzOi8vMTkyLjE2OC45OS4x
MTgxHzAdBgkqhkiG9w0BCQEWEDY2NDEzMTU4NkBxcS5jb20wggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDCJGkxYiVn83vjgfNMNWoVXhprZnpTD+4EAVnX
I8L9c+ij+N+IFu6Q1wgY84y6BKTU8RBVHpxBDp3MNe7POkJ3BVBf+r3fyqkpmoJQ
V6ZK8bokrxUnXyUsMgXgmshkcqAXIcRB3QTrHwMDWHi5yv5VIw/jvEYycNclm9fM
r78ra4UVR3rGv1gGnDag6+865uAkYSGYjGrX1cXdfeha8FIYNp930Zn+hiIOfHTA
GPwMLbgFARUj10F2bGojfc4vOU4pT4nEb9evKvIXY0igjNrTXiLoXGnw7HiiX3rj
VrEqF5/UiZrotLUKAyXie9uGQi/uEoUmQB1a+0wI7PqDBsyVAgMBAAEwDQYJKoZI
hvcNAQELBQADggEBALnQ6DKc2FcUizEa8sOlhEtvLPOuQ89+D6qKLrb5wiLE6CIP
9qwZ0GoyDJN0bRkgbN1rcOd2EkSSf7ecV18xAGrKMg9JTLzsW77hHN8/v1F5xAw6
wQsiP21KN1RGXbjJ1qXjO1ME5Znt8AgVJ4v0wiB9ZEcLVioYYdWXMCbxAuuZdkTm
cQWjpzoxynFPp5FYeRAdsomuSx3dnIalPwE7m6HQYIQ1RP+AVgFcJpPn1UcUhkxr
XMzvZx4AeUmE0Yh1VMhytQzzjE4xpqMFzhkpHHC9hquwga13WluajQApVpoGcQWg
5c+SaX4WZl8O8ud20yD1RfedjZyy2JYCELce8b4=
-----END CERTIFICATE-----

28
src/ssl/key.pem

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCJGkxYiVn83vj
gfNMNWoVXhprZnpTD+4EAVnXI8L9c+ij+N+IFu6Q1wgY84y6BKTU8RBVHpxBDp3M
Ne7POkJ3BVBf+r3fyqkpmoJQV6ZK8bokrxUnXyUsMgXgmshkcqAXIcRB3QTrHwMD
WHi5yv5VIw/jvEYycNclm9fMr78ra4UVR3rGv1gGnDag6+865uAkYSGYjGrX1cXd
feha8FIYNp930Zn+hiIOfHTAGPwMLbgFARUj10F2bGojfc4vOU4pT4nEb9evKvIX
Y0igjNrTXiLoXGnw7HiiX3rjVrEqF5/UiZrotLUKAyXie9uGQi/uEoUmQB1a+0wI
7PqDBsyVAgMBAAECggEAAMyhhRx3+GQg0r1UwTiIwhVDoqR1fOiL08B0MpW5tyMR
8DB9D7MOeMQnPfiD7WNy3usE8/Z2J1AqRFJBUqI/crLlCJ/t5o2zh6DnELMNHFNm
ky3lhIgjcihPxbA+81uAdmay+47ufoLXguf1v8xsX2zQd+D+cnikrdwCOTHTWtt8
JBaXO38kpy3ePQbO0eLNxHmOqLVoOFzRnVWIGsjx14762gTZzxc1XJGIFCor9AuO
qiyK9vpbwi/IEbjm2o80aA/9iGFf0/hE8an96J3XK/nyM7Ne3PGBbg3+BThK6zoc
0qeycUxi7xMJmbznoFOjezyhN6NuNRA6FKg4y4uT2QKBgQDkYllG6QGIwU/FVNiB
TRQdaoOVw79YtEsRiBW+QLcN1Uz81RBu0hHc9UFDsHae52C/6NSyNMdWT4mjMI/Y
rvIwaCX6z544RkAqpXEra97VePlHrtFZa4cMY0ThVoGfhyWfLPJhkkJQ+tKtddB9
QGCNNvTBEBsJf8qeWTXeIqoLuQKBgQDZnhmTevFDyoryrGV0h+oj7XGmarGZ5ZzC
jmB1u4d15dMj1jtmfhjBPWqb5noF+gFfS0Wueo92iTz+RvKbPF9E0SjRHbn541C4
dyHW33W0OcGJuVgBgYE5m/bXvWUUaGNEneIveQtwPbRGNdChVCNuxiRqE6QLSYrX
AHfaR2HNvQKBgFoyiWXbAlMbXcDmiHdQlMAlYACXBYwn8HnlEAlKj8Ez26sDDvFl
n85vQi5UlBSf9vL0JoSXGZ753LcJp9YBCf+aMl3C/47kF9V/75khiikvFOx9m4rA
T97RmdOREbKKco5R9wwX8n7/AJOnMwc0lK1Q5gsVrEftI320Z/TqJdRpAoGBAM0T
cIbLFRINjWOjkvh+HOg+mxt48GVXPKMcCUrprn4sVw0ulkvACRQSDKVeTR/UufZT
uRbz6L6MFi6KLZadDiqL8SsgGGKlFSzBF+KmMwXkZ76iK40/vcfMcpJcqSTSOrov
DzgtmdnsTDbd//4IjFuX3jkYyT7Zzz1N86SAIGsBAoGAViLtO30m8+kbT3hWccuG
7egU7EknB6TROhU/pnKwrfxjRC1xDqd9TxZK9NZoecZ2Hg7uk0IYNM+9nyfD4xzU
xbEfww95RSEebrruexxPWSSlgP7knrw4cj2TyAHziHIcsPMwli6DCLL98WejahQO
We0zuk12n28A5JSJTgjQO2A=
-----END PRIVATE KEY-----

123
src/views/faceRecognition/module/camera.vue

@ -1,13 +1,13 @@
<template>
<div class="camera-container">
<input v-model="imgValue" type="hidden">
<el-button class="subsystembtn" @click="openCamera">打开摄像头</el-button>
<el-button @click="openCamera">打开摄像头</el-button>
<el-select v-if="devices.length > 0" v-model="selectedDeviceId" @change="getStream">
<el-option
v-for="(device,index) in devices"
:key="index"
:value="device.deviceId"
:label="`摄像头 ${device.index + 1}`"
:label="device.label"
/>
</el-select>
@ -29,7 +29,6 @@
</div>
<div v-if="showVideo" id="videoContainer">
<!-- 视频容器增加背景和边框明确显示区域 -->
<div style="display: flex; justify-content: center;">
<div class="video-wrapper">
<video
@ -56,21 +55,23 @@
height="320"
/>
</div>
<button
class="subsystembtn take-btn"
<el-button
type="primary"
class="take-btn"
:style="{display: videoVisible ? 'block' : 'none'}"
:disabled="isLoading || !isVideoPlaying"
@click="takePhoto"
>
拍照
</button>
<button
class="subsystembtn take-btn"
</el-button>
<el-button
type="primary"
class="take-btn"
:style="{display: canvasVisible ? 'block' : 'none',}"
@click="retakePhoto"
>
重拍
</button>
</el-button>
<!-- 调试按钮 -->
<!-- <button
class="debug-btn"
@ -129,23 +130,85 @@ export default {
//
checkBrowserSupport() {
//
if (!navigator.mediaDevices) {
navigator.mediaDevices = {
getUserMedia: function(constraints) {
const getUserMedia = navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'))
}
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject)
})
}
}
} else if (!navigator.mediaDevices.getUserMedia) {
// mediaDevices getUserMedia
const getUserMedia = navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia
if (getUserMedia) {
navigator.mediaDevices.getUserMedia = function(constraints) {
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject)
})
}
}
}
//
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
this.errorMessage = '您的浏览器不支持摄像头访问,请使用Chrome、Firefox或Edge等现代浏览器'
this.errorMessage = '您的浏览器不支持摄像头访问,请升级到最新版Edge/Chrome浏览器'
return false
} else {
this.errorMessage = '' //
}
return true
},
//
enumerateDevices() {
// API
if (!navigator.mediaDevices || typeof navigator.mediaDevices.enumerateDevices !== 'function') {
const error = new Error('浏览器不支持媒体设备枚举API')
this.handleError(error)
return Promise.reject(error)
}
return navigator.mediaDevices.enumerateDevices()
.then(devices => {
console.log('设备列表', devices)
this.handleDevices(devices)
return devices
})
.catch(error => {
this.handleError(error)
return Promise.reject(error)
//
let errorMessage = '获取设备列表失败: '
switch (error.name) {
case 'NotAllowedError':
errorMessage += '没有权限访问媒体设备,请检查浏览器权限设置'
break
case 'NotFoundError':
errorMessage += '未找到媒体设备'
break
case 'NotSupportedError':
errorMessage += '当前环境不支持媒体设备访问,可能需要HTTPS环境'
break
default:
errorMessage += error.message
}
const enhancedError = new Error(errorMessage)
enhancedError.originalError = error
this.handleError(enhancedError)
return Promise.reject(enhancedError)
})
},
@ -155,7 +218,8 @@ export default {
.map((device, index) => ({
...device,
index,
deviceId: device.deviceId || `device-${index}` // ID
deviceId: device.deviceId || `device-${index}`,
label: device.label || `摄像头${index + 1}` // ID
}))
console.log('检测到的摄像头设备:', this.devices)
@ -386,14 +450,6 @@ export default {
this.canvasVisible = true
this.imgValue = this.$refs.canvasUpload.toDataURL('image/jpeg')
this.errorMessage = ''
// uploadFaceImgBase64(this.baseApi, this.imgValue).then(res => {
// console.log(res)
// if (res.data.code === 200) {
// // this.form.personPhotoUrl = res.data.data
// this.$emit('cameraData', this.imgValue, res.data.data)
// }
// })
} catch (e) {
console.error('拍照失败:', e)
this.errorMessage = '拍照失败,请重试'
@ -481,29 +537,6 @@ export default {
background-color: transparent !important;
}
.subsystembtn {
width: 120px;
height: 30px;
background-color: #1d5db2;
border: 0;
color: white;
font-size: 14px;
cursor: pointer;
margin: 5px;
border-radius: 4px;
transition: background-color 0.3s;
}
.subsystembtn:disabled {
background-color: #999;
cursor: not-allowed;
}
.subsystembtn:hover:not(:disabled) {
background-color: #164b8c;
}
.take-btn{
margin: 15px auto 0 auto;
}

63
src/views/faceRecognition/personInfoManage.vue

@ -80,7 +80,7 @@
<input id="upFile" type="file" name="upFile" accept="image/*" @change="changeFile($event)">
<el-button size="small" type="primary"><i class="iconfont icon-shangchuan" />选择上传照片</el-button>
</div>
<el-button type="primary" @click="showCamera"><i class="iconfont icon-yulan" />摄像头拍摄</el-button>
<!-- <el-button type="primary" @click="showCamera"><i class="iconfont icon-yulan" />摄像头拍摄</el-button> -->
</div>
</div>
<div slot="footer" class="dialog-footer">
@ -229,25 +229,6 @@ export default {
mounted() {
},
methods: {
showCamera() {
this.cameraVisible = true
this.$nextTick(() => {
this.$refs.cameraRefs.checkBrowserSupport()
this.$refs.cameraRefs.enumerateDevices()
})
},
handleCamera() {
if (this.$refs.cameraRefs && this.$refs.cameraRefs.imgValue) {
uploadFaceImgBase64(this.baseApi, this.$refs.cameraRefs.imgValue).then(res => {
console.log(res)
if (res.data.code === 200) {
this.form.personPhotoUrl = res.data.data
this.imageUrl = this.$refs.cameraRefs.imgValue
this.handleClose()
}
})
}
},
updatePerson() {
this.crud.refresh()
},
@ -277,8 +258,6 @@ export default {
}
FetchPersonInfoById(params).then(res => {
if (res) {
// crud.form = { ...crud.form, ...res }
Object.keys(crud.form).forEach(key => {
this.$set(crud.form, key, null)
})
@ -316,10 +295,9 @@ export default {
this.fileNames = this.file.name
const fileBase64 = await this.getBase64(this.file)
this.imageUrl = fileBase64
upload(this.baseApi + '/api/fileRelevant/uploadFaceImg', this.file).then(res => {
console.log(res)
if (res.data.code === 200) {
this.imageUrl = fileBase64
this.form.personPhotoUrl = res.data.data
}
})
@ -328,11 +306,30 @@ export default {
this.imageUrl = null
}
},
showCamera() {
this.cameraVisible = true
this.$nextTick(() => {
this.$refs.cameraRefs.checkBrowserSupport()
this.$refs.cameraRefs.enumerateDevices()
})
},
handleCamera() {
if (this.$refs.cameraRefs && this.$refs.cameraRefs.imgValue) {
uploadFaceImgBase64(this.baseApi, this.$refs.cameraRefs.imgValue).then(res => {
if (res.data.code === 200) {
this.form.personPhotoUrl = res.data.data
this.imageUrl = this.$refs.cameraRefs.imgValue
this.handleClose()
}
})
} else {
this.form.personPhotoUrl = null
this.imageUrl = null
this.handleClose()
}
},
handleFaceSearch() {
this.faceSearchVisible = true
// this.$nextTick(() => {
// this.$refs.faceSearchRefs
// })
},
handleSelfRegister() {
this.selfRegisterVisible = true
@ -405,18 +402,6 @@ export default {
}
})
},
//
getImgPx(img) {
const image = new Image()
image.src = img
return new Promise((resolve) => {
image.onload = () => {
const width = image.width
const height = image.height
resolve({ width, height })
}
})
},
clickRowHandler(row) {
this.$refs.table.clearSelection()
this.$refs.table.toggleRowSelection(row)

73
src/views/faceRecognition/personRegister.vue

@ -71,7 +71,7 @@
<input id="upFile" type="file" name="upFile" accept="image/*" @change="changeFile($event)">
<el-button size="small" type="primary"><i class="iconfont icon-shangchuan" />选择上传照片</el-button>
</div>
<el-button type="primary"><i class="iconfont icon-yulan" />摄像头拍摄</el-button>
<!-- <el-button type="primary" @click="showCamera"><i class="iconfont icon-yulan" />摄像头拍摄</el-button> -->
</div>
</div>
<div slot="footer" class="dialog-footer">
@ -133,6 +133,14 @@
<SelfRegister ref="selfRegisterRefs" :is-dialog-face-regsiter="true" />
</el-dialog>
<el-dialog :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="摄像头拍摄" :visible.sync="cameraVisible" :before-close="handleClose">
<Camera ref="cameraRefs" />
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleCamera">确定</el-button>
</div>
</el-dialog>
</div>
</template>
@ -145,15 +153,16 @@ import pagination from '@crud/Pagination'
import FaceSearch from './module/faceSearch'
import SelfRegister from './module/selfRegister'
import BatchImport from './module/batchImport'
import Camera from './module/camera'
// import { exportFile } from '@/utils/index'
// import qs from 'qs'
import { mapGetters } from 'vuex'
import { upload } from '@/utils/upload'
import { upload, uploadFaceImgBase64 } from '@/utils/upload'
const defaultForm = { personId: null, libcode: null, barcode: null, personName: null, personPhotoUrl: null, idCard: null, phone: null, personSex: 1, personType: 0, personPhotoBase64: null }
export default {
name: 'PersonInfoManage',
components: { pagination, crudOperation, rrOperation, FaceSearch, SelfRegister, BatchImport },
components: { pagination, crudOperation, rrOperation, FaceSearch, SelfRegister, BatchImport, Camera },
cruds() {
return CRUD({ title: '人员信息', idField: 'personId', url: 'api/person/initPersonInfo', crudMethod: { ...crudFace }, optShow: {
add: true,
@ -196,7 +205,8 @@ export default {
filePath: '',
imageUrl: null,
selfRegisterVisible: false,
faceSearchVisible: false
faceSearchVisible: false,
cameraVisible: false
}
},
computed: {
@ -224,6 +234,12 @@ export default {
},
//
[CRUD.HOOK.beforeToAdd](crud, form) {
this.imageUrl = null
Object.keys(crud.form).forEach(key => {
this.$set(crud.form, key, null)
})
this.crud.form.personSex = 1
this.crud.form.personType = 0
},
//
[CRUD.HOOK.beforeToEdit](crud, form) {
@ -233,8 +249,6 @@ export default {
}
FetchPersonInfoById(params).then(res => {
if (res) {
// crud.form = { ...crud.form, ...res }
Object.keys(crud.form).forEach(key => {
this.$set(crud.form, key, null)
})
@ -272,10 +286,9 @@ export default {
this.fileNames = this.file.name
const fileBase64 = await this.getBase64(this.file)
this.imageUrl = fileBase64
upload(this.baseApi + '/api/fileRelevant/uploadFaceImg', this.file).then(res => {
console.log(res)
if (res.data.code === 200) {
this.imageUrl = fileBase64
this.form.personPhotoUrl = res.data.data
}
})
@ -284,11 +297,30 @@ export default {
this.imageUrl = null
}
},
showCamera() {
this.cameraVisible = true
this.$nextTick(() => {
this.$refs.cameraRefs.checkBrowserSupport()
this.$refs.cameraRefs.enumerateDevices()
})
},
handleCamera() {
if (this.$refs.cameraRefs && this.$refs.cameraRefs.imgValue) {
uploadFaceImgBase64(this.baseApi, this.$refs.cameraRefs.imgValue).then(res => {
if (res.data.code === 200) {
this.form.personPhotoUrl = res.data.data
this.imageUrl = this.$refs.cameraRefs.imgValue
this.handleClose()
}
})
} else {
this.form.personPhotoUrl = null
this.imageUrl = null
this.handleClose()
}
},
handleFaceSearch() {
this.faceSearchVisible = true
// this.$nextTick(() => {
// this.$refs.faceSearchRefs
// })
},
handleSelfRegister() {
this.selfRegisterVisible = true
@ -361,18 +393,6 @@ export default {
}
})
},
//
getImgPx(img) {
const image = new Image()
image.src = img
return new Promise((resolve) => {
image.onload = () => {
const width = image.width
const height = image.height
resolve({ width, height })
}
})
},
clickRowHandler(row) {
this.$refs.table.clearSelection()
this.$refs.table.toggleRowSelection(row)
@ -384,6 +404,13 @@ export default {
this.$refs.faceSearchRefs.searchPerson = {}
this.$refs.faceSearchRefs.btnLoading = false
}
this.cameraVisible = false
if (this.$refs.cameraRefs) {
this.$refs.cameraRefs.stopStream()
this.$refs.cameraRefs.imgValue = ''
this.$refs.cameraRefs.errorMessage = ''
this.$refs.cameraRefs.showVideo = false
}
}
}
}

6
vue.config.js

@ -27,6 +27,12 @@ module.exports = {
lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,
devServer: {
// https: {
// // 读取私钥文件
// key: require('fs').readFileSync(path.resolve(__dirname, './src/ssl/key.pem')),
// // 读取证书文件
// cert: require('fs').readFileSync(path.resolve(__dirname, './src/ssl/cert.pem'))
// },
port: port,
open: true,
overlay: {

Loading…
Cancel
Save