Browse Source

人脸识别

master
xuhuajiao 1 month ago
parent
commit
15e6ecf155
  1. 39
      src/api/faceRecognition/index.js
  2. 11
      src/utils/upload.js
  3. 63
      src/views/faceRecognition/module/batchImport.vue
  4. 140
      src/views/faceRecognition/module/faceSearch.vue
  5. 151
      src/views/faceRecognition/personInfoManage.vue

39
src/api/faceRecognition/index.js

@ -1,15 +1,5 @@
import request from '@/utils/request'
import qs from 'qs'
// // 编辑智慧大屏后台参数
// export function FetchEditScreenSetting(parameter) {
// return request({
// url: 'api/screenSetting/editScreenSetting',
// method: 'post',
// data: parameter
// })
// }
export function add(data) {
return request({
url: 'api/person/editPersonInfo',
@ -42,18 +32,28 @@ export function FetchPersonInfoById(params) {
})
}
// 导入批量添加人脸基本信息预查看
export function FetchPreviewFileInfo(params) {
// 更改人员类型
export function FetchChangePersonInfo(data) {
return request({
url: 'api/person/previewFileInfo' + '?' + qs.stringify(params, { indices: false }),
method: 'get'
url: 'api/person/changePersonInfo',
method: 'post',
data
})
}
// 更改人员类型
export function FetchChangePersonInfo(data) {
// 人脸查询
export function FetchFindPersonFace(data) {
return request({
url: 'api/person/changePersonInfo',
url: 'api/person/findPersonFace',
method: 'post',
data
})
}
// 批量上传人员
export function FetchBatchSavePersonInfos(data) {
return request({
url: 'api/person/batchSavePersonInfos',
method: 'post',
data
})
@ -64,6 +64,7 @@ export default {
edit,
del,
FetchPersonInfoById,
FetchPreviewFileInfo,
FetchChangePersonInfo
FetchChangePersonInfo,
FetchFindPersonFace,
FetchBatchSavePersonInfos
}

11
src/utils/upload.js

@ -144,3 +144,14 @@ export function mobileUpload(api, file, params) {
}
return axios.post(api, data, config)
}
// 人脸查询
export function uploadPerson(api, file, params) {
var data = new FormData()
data.append('file', file)
data.append('libcode', params.libcode)
const config = {
headers: { 'Authorization': getToken() }
}
return axios.post(api, data, config)
}

63
src/views/faceRecognition/module/batchImport.vue

@ -4,7 +4,7 @@
<div class="setting-dialog" style="display: flex; justify-content: center; flex-direction: column; align-items: center;">
<p>注意批量导入需在指定的模板文件内完成数据录入后上传文件再由系统执行导入操作</p>
<div class="btn-wrap">
<el-button size="mini"> <i class="iconfont icon-xiazai" />下载模板</el-button>
<el-button size="mini" @click="downloadTemplate"> <i class="iconfont icon-xiazai" />下载模板</el-button>
<el-button size="mini" :loading="isParsing" @click="openFileDialog">
<i class="iconfont icon-piliangchengjian" />导入文件
</el-button>
@ -32,17 +32,21 @@
<el-table ref="table" highlight-current-row style="width: 100%;" height="calc(100vh - 330px)" :data="tableData">
<el-table-column type="index" align="center" width="55" />
<el-table-column prop="barcode" label="证号" />
<el-table-column prop="libcode" label="馆代码" />
<el-table-column prop="personName" label="姓名" />
<el-table-column prop="idcard" label="身份证" />
<el-table-column prop="personSex" label="性别" />
<el-table-column prop="idcard" label="身份证号" />
<el-table-column prop="phone" label="联系方式" />
<el-table-column prop="personSex" label="性别" width="60">
<template slot-scope="scope">
<span>{{ scope.row.personSex === 1 ? '男' : '女' }}</span>
</template>
</el-table-column>
<el-table-column prop="personPhotoBase64" label="照片">
<template slot-scope="scope">
<img v-if="scope.row.personPhotoBase64" :src="scope.row.personPhotoBase64" style="width: 100px; height: 100px;">
<img v-if="scope.row.personPhotoBase64" :src="`data:image/jpeg;base64,${scope.row.personPhotoBase64}`" style="width: 100px; height: 100px;">
<img v-else src="~@/assets/images/cover-bg.png" style="width: 100px; height: 100px;">
</template>
</el-table-column>
<el-table-column label="操作" align="center" prop="columnStatus" width="240">
<el-table-column label="操作" align="center" prop="columnStatus" width="100">
<template slot-scope="scope">
<el-button size="mini" style="padding: 5px;" @click="handleDeleteRow(scope.row)">
<i class="iconfont icon-shanchu1" />
@ -61,7 +65,11 @@
</template>
<script>
import { FetchBatchSavePersonInfos } from '@/api/faceRecognition/index'
import { upload } from '@/utils/upload'
import qs from 'qs'
import { exportFile } from '@/utils/index'
import { mapGetters } from 'vuex'
export default {
data() {
return {
@ -77,6 +85,12 @@ export default {
progressTimer: null //
}
},
computed: {
...mapGetters([
'baseApi',
'user'
])
},
beforeDestroy() {
if (this.progressTimer) {
clearInterval(this.progressTimer)
@ -87,6 +101,9 @@ export default {
this.$refs.fileInput.value = ''
this.$refs.fileInput.click()
},
downloadTemplate() {
exportFile(this.baseApi + '/api/person/exportSavePersonTemplate?' + qs.stringify({}, { indices: false }))
},
updateProgress() {
this.progressTimer = setInterval(() => {
if (this.parsingProgress < 90) {
@ -148,21 +165,37 @@ export default {
})
},
handleDeleteRow(currentRow) {
this.$confirm('此操作将删除当前所选' + this.crud.title + '<span>你是否还要继续?</span>', '提示', {
confirmButtonText: '确定',
this.$confirm('此操作将删除所选数据' + '<span>你是否还要继续?</span>', '提示', {
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning'
type: 'warning',
dangerouslyUseHTMLString: true
}).then(() => {
this.tableData = this.tableData.filter(
item => item.barcode !== currentRow.barcode
)
this.tableData = this.tableData.filter(item => item !== currentRow)
this.$message({ message: '删除成功', type: 'success', offset: 8 })
}).catch(() => {
this.$message({ message: '取消删除', type: 'info', offset: 8 })
})
},
handleComfired() {
this.btnLoading = true
const params = {
'libcode': this.user.fonds.fondsNo,
'dtos': this.tableData
}
console.log('params', params)
FetchBatchSavePersonInfos(params).then((res) => {
if (res) {
this.$message({ message: '人员数据导入成功', type: 'success', offset: 8 })
this.$emit('refresh')
} else {
this.$message({ message: '人员数据导入失败', type: 'error', offset: 8 })
}
this.handleClose()
}).catch(error => {
console.error('导入失败', error)
this.btnLoading = false
})
},
handleClose() {
this.batchImportVisible = false
@ -175,6 +208,7 @@ export default {
this.isParsing = false
this.parsingProgress = 0
this.tableData = []
this.btnLoading = false
}
}
}
@ -205,4 +239,7 @@ export default {
color: #666;
font-size: 14px;
}
::v-deep .el-dialog{
width: 900px !important;
}
</style>

140
src/views/faceRecognition/module/faceSearch.vue

@ -1,68 +1,132 @@
<template>
<el-dialog :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="人脸查询" :visible.sync="faceSearchVisible">
<el-dialog :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="人脸查询" :visible.sync="faceSearchVisible" :before-close="handleClose">
<div class="setting-dialog" style="display: flex; justify-content: flex-start;">
<div class="upload-img-input">
<input ref="fileInput" type="file" @change="changeFile($event)">
<input ref="fileInput" type="file" style="cursor: pointer;" @change="changeFile($event)">
<div class="upload-libImg">
<img src="~@/assets/images/user.jpg" alt="">
<img :src="imageUrl || require('@/assets/images/user.jpg')" alt="人员照片">
<span>点击上传照片进行比对</span>
</div>
</div>
<ul class="face-result-info">
<li><span>馆代码</span>ftzn</li>
<li><span>读者证号</span>78198239</li>
<li><span>姓名</span>张三</li>
<li><span>身份证号</span>420105198500000000</li>
<li><span>性别</span></li>
<li><span>联系方式</span>15800000000</li>
<li><span>类型</span>普通用户</li>
<ul v-loading="isLoading" class="face-result-info">
<li><span>馆代码</span>{{ searchPerson?.libcode || '-' }}</li>
<li><span>读者证号</span>{{ searchPerson?.barcode || '-' }}</li>
<li><span>姓名</span>{{ searchPerson?.personName || '-' }}</li>
<li><span>身份证号</span>{{ searchPerson?.idCard || '-' }}</li>
<li><span>性别</span>{{ searchPerson?.personSex !== undefined ? (searchPerson.personSex === 1 ? '男' : '女') : '-' }}</li>
<li><span>联系方式</span>{{ searchPerson?.phone || '-' }}</li>
<li><span>类型</span>
<template v-if="searchPerson?.personType !== undefined && searchPerson?.personType !== null">
<el-tag :type="getPersonTypeTagType(searchPerson.personType)">
{{ getPersonTypeLabel(searchPerson.personType) }}
</el-tag>
</template>
<template v-else>-</template>
</li>
</ul>
</div>
</el-dialog>
</template>
<script>
// import { mapGetters } from 'vuex'
// import { getCurrentTime } from '@/utils/index'
import { upload } from '@/utils/upload'
import { mapGetters } from 'vuex'
import { uploadPerson } from '@/utils/upload'
export default {
data() {
return {
faceSearchVisible: false,
file: null,
fileNames: '',
fileSize: '',
filePath: '',
px: '',
imageUrl: null
imageUrl: null,
searchPerson: {},
isLoading: false
}
},
computed: {
...mapGetters([
'baseApi',
'user'
])
},
mounted() {
},
methods: {
getPersonTypeLabel(typeValue) {
if (typeValue === undefined || typeValue === null) {
return ''
}
const typeMap = [
{ value: 0, label: '普通用户' },
{ value: 1, label: 'VIP用户' },
{ value: 2, label: '黑名单用户' }
]
const typeItem = typeMap.find(item => item.value === typeValue)
return typeItem ? typeItem.label : '-'
},
//
getPersonTypeTagType(typeValue) {
// type
switch (typeValue) {
case 0:
return '' // -
case 1:
return 'success' // VIP - 绿
case 2:
return 'info' // -
default:
return '' //
}
},
async changeFile(e) {
const file = e.target.files[0]
if (file && file.type.startsWith('image/')) {
this.file = e.target.files[0]
this.fileSize = this.file.size
this.fileNames = this.file.name
const fileBase64 = await this.getBase64(this.file)
const res = await this.getImgPx(fileBase64)
this.imageUrl = fileBase64
this.px = res.width + 'px*' + res.height + 'px'
upload(this.baseApi + '/api/fileRelevant/uploadOtherImg', this.file).then(res => {
console.log(res)
// loading
this.isLoading = true
//
this.searchPerson = {}
try {
const fileBase64 = await this.getBase64(this.file)
this.imageUrl = fileBase64
const params = {
'libcode': this.user.fonds.fondsNo
}
//
const res = await uploadPerson(this.baseApi + '/api/person/findPersonFace', this.file, params)
if (res.data.code === 200) {
this.filePath = res.data.data
// this.$emit('childCover', res.data.data)
const result = JSON.parse(res.data.data)
if (result.personId) {
this.searchPerson = result
} else {
this.$message({ message: res.data.data, type: 'error', offset: 8 })
}
} else {
this.$message({ message: '查询失败,请重试', type: 'error', offset: 8 })
}
})
} catch (error) {
console.error('人脸查询出错:', error)
this.$message({ message: '查询过程出错,请重试', type: 'error', offset: 8 })
} finally {
// loading
this.isLoading = false
}
} else {
this.$message({ message: '只可上传图片', type: 'error', offset: 8 })
this.imageUrl = null
}
//
e.target.value = ''
},
// base64
getBase64(file) {
@ -74,17 +138,11 @@ 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 })
}
})
handleClose() {
this.faceSearchVisible = false
this.imageUrl = null
this.searchPerson = {}
this.btnLoading = false
}
}
}
@ -148,4 +206,8 @@ export default {
}
}
}
::v-deep .el-tag{
width: auto !important;
margin: 0 !important;
}
</style>

151
src/views/faceRecognition/personInfoManage.vue

@ -51,8 +51,8 @@
<el-form-item label="姓名" prop="personName">
<el-input v-model="form.personName" placeholder="请输入" />
</el-form-item>
<el-form-item label="身份证号" prop="idcard">
<el-input v-model="form.idcard" placeholder="请输入" />
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="form.idCard" placeholder="请输入" />
</el-form-item>
<el-form-item label="联系方式" prop="phone">
<el-input v-model="form.phone" placeholder="请输入" />
@ -89,25 +89,42 @@
</div>
</div>
</el-dialog>
<el-table ref="table" v-loading="crud.loading" highlight-current-row style="width: 100%;" height="calc(100vh - 330px)" :data="crud.data" @selection-change="crud.selectionChangeHandler">
<el-table ref="table" v-loading="crud.loading" highlight-current-row style="width: 100%;" height="calc(100vh - 330px)" :data="crud.data" @selection-change="crud.selectionChangeHandler" @row-click="clickRowHandler">
<el-table-column type="selection" align="center" width="55" />
<el-table-column type="index" label="序号" width="55" />
<el-table-column prop="personName" label="姓名" />
<el-table-column prop="code" label="性别" />
<el-table-column prop="personSex" label="性别" width="60">
<template slot-scope="scope">
<span>{{ scope.row.personSex === 1 ? '男' : '女' }}</span>
</template>
</el-table-column>
<el-table-column prop="barcode" label="读者证号" />
<el-table-column prop="idcard" label="身份证号" />
<el-table-column prop="libcode" label="馆代码" />
<el-table-column prop="columnOrders" label="已存照片" align="center" />
<el-table-column prop="columnOrders" label="用户类型" align="center" />
<el-table-column prop="idCard" label="身份证号" />
<el-table-column prop="libcode" label="馆代码" width="100" />
<el-table-column prop="personPhotoUrl" label="已存照片" align="center">
<template slot-scope="scope">
<!-- <img v-if="scope.row.personPhotoUrl === '' || scope.row.personPhotoUrl === null" src="~@/assets/images/cover-bg.png" alt="" style="display:block; height: 60px;">
<img v-else :src="baseApi + '/api/fileRelevant/getFaceImg?imgId=' + scope.row.personPhotoUrl" alt="" style="display:block; height: 60px;"> -->
<span v-if="scope.row.personPhotoUrl === '' || scope.row.personPhotoUrl === null"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column prop="personType" label="用户类型" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.personType === 0">普通用户</el-tag>
<el-tag v-if="scope.row.personType === 1" type="success">VIP用户</el-tag>
<el-tag v-if="scope.row.personType === 2" type="info">黑名单用户</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" prop="columnStatus" width="240">
<template slot-scope="scope">
<el-button size="mini" style="padding: 5px;" @click="addBlackOrVipUser(scope.row,0)">
<el-button :class="scope.row.personType === 2 ? 'remove-black-btn' : ''" size="mini" style="padding: 5px;" @click="addBlackOrVipUser(scope.row,0)">
<i class="iconfont icon-heimingdan" />
加入黑名单
{{ scope.row.personType === 2 ? '移出黑名单' : '加入黑名单' }}
</el-button>
<el-button size="mini" style="padding: 5px;" @click="addBlackOrVipUser(scope.row,1)">
<el-button :class="scope.row.personType === 1 ? 'remove-vip-btn' : ''" size="mini" style="padding: 5px;" @click="addBlackOrVipUser(scope.row,1)">
<i class="iconfont icon-yooxi" />
加入VIP
{{ scope.row.personType === 1 ? '移出VIP' : '加入VIP' }}
</el-button>
</template>
</el-table-column>
@ -117,12 +134,12 @@
<FaceSearch ref="faceSearchRefs" />
<SelfRegister ref="selfRegisterRefs" />
<BatchImport ref="batchImportRefs" />
<BatchImport ref="batchImportRefs" @refresh="updatePerson" />
</div>
</template>
<script>
import crudFace from '@/api/faceRecognition/index'
import crudFace, { FetchPersonInfoById, FetchChangePersonInfo } from '@/api/faceRecognition/index'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
@ -135,12 +152,12 @@ import BatchImport from './module/batchImport'
import { mapGetters } from 'vuex'
import { upload } 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 }
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 },
cruds() {
return CRUD({ title: '人员信息', url: 'api/person/initPersonInfo', crudMethod: { ...crudFace }, optShow: {
return CRUD({ title: '人员信息', idField: 'personId', url: 'api/person/initPersonInfo', crudMethod: { ...crudFace }, optShow: {
add: true,
edit: true,
del: false,
@ -193,6 +210,9 @@ export default {
mounted() {
},
methods: {
updatePerson() {
this.crud.refresh()
},
[CRUD.HOOK.beforeRefresh]() {
this.crud.query.libcode = this.user.fonds.fondsNo
},
@ -207,6 +227,33 @@ export default {
},
//
[CRUD.HOOK.beforeToEdit](crud, form) {
this.imageUrl = null
const params = {
'id': form.personId
}
FetchPersonInfoById(params).then(res => {
if (res) {
// crud.form = { ...crud.form, ...res }
Object.keys(crud.form).forEach(key => {
this.$set(crud.form, key, null)
})
Object.keys(res).forEach(key => {
this.$set(crud.form, key, res[key])
})
if (this.form.personPhotoUrl) {
this.imageUrl = this.baseApi + '/api/fileRelevant/getFaceImg?imgId=' + this.form.personPhotoUrl
} else {
this.imageUrl = require('@/assets/images/user.jpg')
}
} else {
this.$message({ message: '获取数据失败', type: 'error', offset: 8 })
}
}).catch((err) => {
this.$message({ message: '获取数据失败', type: 'error', offset: 8 })
console.error(err)
})
},
//
[CRUD.HOOK.afterValidateCU](crud) {
@ -256,35 +303,47 @@ export default {
this.crud.delAllLoading = true
const ids = []
datas.forEach(val => {
ids.push(val.fondsId)
ids.push(val.personId)
})
crudFace.del(ids).then(() => {
this.$message({ message: '删除成功', type: 'success', offset: 8 })
this.crud.delAllLoading = false
this.crud.refresh()
}).catch(err => {
this.crud.delAllLoading = false
console.log(err)
})
// crudColumn.del(ids).then(() => {
// this.$message({ message: '', type: 'success', offset: 8 })
// this.crud.delAllLoading = false
// this.crud.refresh()
// }).catch(err => {
// this.crud.delAllLoading = false
// console.log(err)
// })
}).catch(() => {
})
},
addBlackOrVipUser(data, type) {
const message = type === 1 ? 'VIP' : '黑名单'
this.$confirm('此操作将所选用户加入' + message + '<span>你是否还要继续?</span>', '提示', {
this.$confirm('此操作将所选用户加入 / 移出 ' + message + '<span>你是否还要继续?</span>', '提示', {
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true
}).then(() => {
// crudColumn.FetchUpdateFondsStatus(data).then(res => {
// this.$message({ message: '', type: 'success', offset: 8 })
// }).catch(() => {
// data.fondsStatus = !data.fondsStatus
// })
if (data.personType === 0 && type === 1) {
data.personType = 1
} else if (data.personType === 0 && type === 0) {
data.personType = 2
} else if (data.personType === 1 && type === 1) {
data.personType = 0
} else if (data.personType === 1 && type === 0) {
data.personType = 2
} else if (data.personType === 2 && type === 1) {
data.personType = 1
} else if (data.personType === 2 && type === 0) {
data.personType = 0
}
FetchChangePersonInfo(data).then(res => {
this.$message({ message: '修改成功', type: 'success', offset: 8 })
}).catch(() => {
this.$message({ message: '修改失败', type: 'error', offset: 8 })
})
}).catch(() => {
this.$message({ message: '已取消修改', offset: 8 })
// data.fondsStatus = data.fondsStatus ? 0 : 1
})
},
getBase64(file) {
@ -307,6 +366,10 @@ export default {
resolve({ width, height })
}
})
},
clickRowHandler(row) {
this.$refs.table.clearSelection()
this.$refs.table.toggleRowSelection(row)
}
}
}
@ -334,4 +397,28 @@ export default {
margin: 20px 0;
}
}
::v-deep .el-tag{
&.el-tag--small{
color: #0348f3 !important;
background-color: #eef5fe !important;
border-color: #eef5fe !important;
}
&.el-tag--success {
background-color: #e7faf0 !important;
border-color: #d0f5e0!important;
color: #13ce66!important;
}
&.el-tag--info {
background-color: #f4f4f5 !important;
border-color: #f4f4f5!important;
color: #909399!important;
}
}
.remove-black-btn,
.remove-vip-btn{
border-color: #909399;
color: #909399;
}
</style>
Loading…
Cancel
Save