Browse Source

人脸识别

master
xuhuajiao 1 month ago
parent
commit
195867bf82
  1. 11
      src/api/faceRecognition/index.js
  2. BIN
      src/assets/images/user.jpg
  3. BIN
      src/assets/images/user2.jpg
  4. 168
      src/views/faceRecognition/faceRecLog.vue
  5. 157
      src/views/faceRecognition/module/faceSearch.vue
  6. 44
      src/views/faceRecognition/module/selfRegister.vue
  7. 37
      src/views/faceRecognition/personInfoManage.vue
  8. 438
      src/views/faceRecognition/personRegister.vue

11
src/api/faceRecognition/index.js

@ -59,6 +59,14 @@ export function FetchBatchSavePersonInfos(data) {
})
}
// 人脸识别日志操作类别列表
export function FetchSnapshotOperation(params) {
return request({
url: 'api/person/getSnapshotOperation' + '?' + qs.stringify(params, { indices: false }),
method: 'get'
})
}
export default {
add,
edit,
@ -66,5 +74,6 @@ export default {
FetchPersonInfoById,
FetchChangePersonInfo,
FetchFindPersonFace,
FetchBatchSavePersonInfos
FetchBatchSavePersonInfos,
FetchSnapshotOperation
}

BIN
src/assets/images/user.jpg

Before

Width: 110  |  Height: 160  |  Size: 2.9 KiB

After

Width: 240  |  Height: 340  |  Size: 6.2 KiB

BIN
src/assets/images/user2.jpg

After

Width: 110  |  Height: 160  |  Size: 2.9 KiB

168
src/views/faceRecognition/faceRecLog.vue

@ -4,7 +4,7 @@
<div class="head-container">
<div class="head-search" style="align-items: center;">
<!-- 搜索 -->
<el-select v-model="query.status" clearable size="small" placeholder="用户类型" class="filter-item" style="width: 120px;" @change="crud.toQuery">
<el-select v-model="query.personType" clearable size="small" placeholder="用户类型" class="filter-item" style="width: 140px;" @change="crud.toQuery">
<i slot="prefix" class="iconfont icon-zhuangtai" />
<el-option v-for="item in userTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
@ -27,17 +27,20 @@
/>
</el-select>
</el-input>
<el-select v-model="query.status" clearable size="small" placeholder="操作类型" class="filter-item" style="width: 150px;" @change="crud.toQuery">
<el-select v-model="query.snapshotOperation" clearable size="small" placeholder="操作类型" class="filter-item" style="width: 150px;" @change="crud.toQuery">
<i slot="prefix" class="iconfont icon-zhuangtai" />
<el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<date-range-picker v-model="blurryTime" class="date-item" />
<rrOperation />
<rrOperation>
<template v-slot:right>
<el-button class="filter-item filter-refresh" size="mini" type="warning" icon="el-icon-refresh-left" @click="resetQuery()">重置</el-button>
</template>
</rrOperation>
</div>
<crudOperation :permission="permission">
<template v-slot:right>
<!-- :disabled="crud.selections.length === 0" -->
<el-button :loading="crud.downloadLoading" size="mini" @click="doExport(crud.selections)">
<el-button :loading="crud.downloadLoading" size="mini" @click="doExport">
<i class="iconfont icon-daochu" />
导出
</el-button>
@ -49,14 +52,35 @@
<span class="left-bottom-line" />
<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-column type="index" label="序号" width="55" />
<el-table-column prop="name" label="操作者(读者证号/读者姓名)" />
<el-table-column prop="name" label="用户类型" />
<el-table-column prop="code" label="操作设备(设备编号/设备名称)" />
<el-table-column prop="type" label="所属机构(机构编号/机构名称)" />
<el-table-column prop="cardNum" label="操作类型" />
<el-table-column :show-overflow-tooltip="true" prop="createTime" label="操作时间">
<el-table-column prop="personName" label="操作者(读者证号/读者姓名)">
<template slot-scope="scope">
<div v-if="scope.row.barcode && scope.row.personName">{{ scope.row.barcode }} / {{ scope.row.personName }}</div>
<div v-else>-</div>
</template>
</el-table-column>
<el-table-column prop="personType" label="用户类型" width="120">
<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 prop="deviceName" label="操作设备(设备编号/设备名称)">
<template slot-scope="scope">
<div v-if="scope.row.deviveCode && scope.row.deviceName">{{ scope.row.deviveCode }} / {{ scope.row.deviceName }}</div>
<div v-else>-</div>
</template>
</el-table-column>
<el-table-column prop="fondsName" label="所属机构(机构编号/机构名称)">
<template slot-scope="scope">
<div v-if="scope.row.libcode && scope.row.fondsName">{{ scope.row.libcode }} / {{ scope.row.fondsName }}</div>
<div v-else>-</div>
</template>
</el-table-column>
<el-table-column prop="snapshotOperation" label="操作类型" />
<el-table-column :show-overflow-tooltip="true" prop="snapshotTime" label="操作时间">
<template slot-scope="scope">
<div>{{ scope.row.createTime | parseTime }}</div>
<div>{{ scope.row.snapshotTime | parseTime }}</div>
</template>
</el-table-column>
</el-table>
@ -67,31 +91,30 @@
</template>
<script>
import crudColumn from '@/api/inquiryMachine/column'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import crudFace, { FetchSnapshotOperation } from '@/api/faceRecognition/index'
import CRUD, { presenter, header, crud } from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import DateRangePicker from '@/components/DateRangePicker'
// import { exportFile } from '@/utils/index'
// import qs from 'qs'
import { exportFile } from '@/utils/index'
import qs from 'qs'
import { mapGetters } from 'vuex'
const defaultForm = { id: null, columnName: null, columnNo: null, columnType: 1, columnOrders: 999, columnContentType: 1, columnStatus: 1, columnRemarks: null }
export default {
name: 'PersonInfoManage',
components: { pagination, crudOperation, rrOperation, DateRangePicker },
cruds() {
return CRUD({ title: '人脸识别日志', url: 'api/fonds/initFondsList', crudMethod: { ...crudColumn }, optShow: {
return CRUD({ title: '人脸识别日志', url: 'api/person/initFaceRecognizeLog', crudMethod: { ...crudFace }, optShow: {
add: false,
edit: false,
del: false,
reset: true,
reset: false,
download: false,
group: false
}})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
mixins: [presenter(), header(), crud()],
data() {
return {
permission: {
@ -100,21 +123,17 @@ export default {
del: ['admin', 'column:del']
},
userTypeOptions: [
{ key: '1', display_name: '普通用户' },
{ key: '2', display_name: 'VIP用户' },
{ key: '3', display_name: '黑名单用户' }
],
enabledTypeOptions: [
{ key: '1', display_name: '借书登录' },
{ key: '2', display_name: '信息绑定登录' },
{ key: '3', display_name: '查询续借登录' }
{ key: 0, display_name: '普通用户' },
{ key: 1, display_name: 'VIP用户' },
{ key: 2, display_name: '黑名单用户' }
],
enabledTypeOptions: [],
blurryTime: [],
keyWord: '',
optionVal: 'username',
optionVal: 'personSearch',
options: [
{ value: 'username', label: '读者证号/读者姓名' },
{ value: 'account', label: '设备名称/设备编号' }
{ value: 'personSearch', label: '读者证号/读者姓名' },
{ value: 'terminalSearch', label: '设备名称/设备编号' }
]
}
},
@ -125,13 +144,13 @@ export default {
])
},
created() {
this.getSnapshotOperation()
},
mounted() {
},
methods: {
[CRUD.HOOK.beforeRefresh]() {
console.log('this', this.keyWord)
console.log('this.optionVal', this.optionVal)
this.crud.query.libcode = this.user.fonds.fondsNo
this.crud.query[this.optionVal] = this.keyWord
if (this.blurryTime) {
this.crud.query.startTime = this.blurryTime[0]
@ -143,16 +162,15 @@ export default {
},
[CRUD.HOOK.afterRefresh]() {
},
//
[CRUD.HOOK.beforeToAdd](crud, form) {
},
//
[CRUD.HOOK.beforeToEdit](crud, form) {
},
//
[CRUD.HOOK.afterValidateCU](crud) {
console.log(crud.form)
return true
resetQuery() {
this.keyWord = ''
this.crud.query[this.optionVal] = this.keyWord
this.blurryTime = null
this.crud.query.startTime = null
this.crud.query.endTime = null
this.crud.query.personType = null
this.crud.query.snapshotOperation = null
this.crud.toQuery()
},
searchChange(val) {
if (val) {
@ -164,25 +182,48 @@ export default {
})
}
},
doExport(data) {
console.log(data)
crud.downloadLoading = true
this.$confirm('此操作将导出所选数据' + '<span>你是否还要继续?</span>', '提示', {
getSnapshotOperation() {
FetchSnapshotOperation({ 'libcode': this.user.fonds.fondsNo }).then(res => {
this.enabledTypeOptions = res.map(item => {
return {
key: item,
display_name: item
}
})
}).catch(err => {
console.log(err)
})
},
doExport() {
console.log('doExport', this.crud.page.total)
if (this.crud.page.total > 10000) {
this.handleExport('导出数据大于10000条,时间可能较长')
} else {
this.handleExport('此操作将导出所有数据')
}
},
handleExport(message) {
this.crud.downloadLoading = true
this.$confirm(message + '<span>你是否还要继续?</span>', '提示', {
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true
}).then(() => {
const ids = []
data.forEach(val => {
ids.push(val.id)
})
const params = {
'ids': ids
'libcode': this.user.fonds.fondsNo,
'personType': this.crud.query.personType,
'snapshotOperation': this.crud.query.snapshotOperation,
'personSearch': this.optionVal === 'personSearch' ? this.keyWord : null,
'terminalSearch': this.optionVal === 'terminalSearch' ? this.keyWord : null,
'startTime': this.blurryTime.length !== 0 ? this.blurryTime[0].split(' ')[0] : null,
'endTime': this.blurryTime.length !== 0 ? this.blurryTime[1].split(' ')[0] : null
}
console.log('params', params)
// exportFile(this.baseApi + '/api/log/downloadErrorLog?' + qs.stringify(params, { indices: false }))
console.log('exportFile', params)
exportFile(this.baseApi + '/api/person/downloadFaceRecognizeLog?' + qs.stringify(params, { indices: false, allowDots: true, skipNulls: false }))
this.crud.downloadLoading = false
}).catch(() => {
console.log('取消')
})
}
}
@ -193,4 +234,23 @@ export default {
::v-deep .input-prepend .el-input__inner{
padding-left: 160px;
}
::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;
}
}
</style>

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

@ -1,40 +1,44 @@
<template>
<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" style="cursor: pointer;" @change="changeFile($event)">
<div class="upload-libImg">
<img :src="imageUrl || require('@/assets/images/user.jpg')" alt="人员照片">
<span>点击上传照片进行比对</span>
</div>
<!-- <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=""> -->
<div :class=" isDiaglogFaceSearch ? 'setting-dialog': 'app-container row-container'">
<div class="upload-img-input">
<input ref="fileInput" type="file" style="cursor: pointer;" @change="changeFile($event)">
<div class="upload-libImg">
<img :src="imageUrl || require('@/assets/images/user.jpg')" alt="人员照片">
<span>点击上传照片进行比对</span>
</div>
<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>
<ul v-loading="isLoading" class="face-result-info">
<li><span class="person-label">馆代码</span>{{ searchPerson?.libcode || '-' }}</li>
<li><span class="person-label">读者证号</span>{{ searchPerson?.barcode || '-' }}</li>
<li><span class="person-label">姓名</span>{{ searchPerson?.personName || '-' }}</li>
<li><span class="person-label">身份证号</span>{{ searchPerson?.idCard || '-' }}</li>
<li><span class="person-label">性别</span>{{ searchPerson?.personSex !== undefined ? (searchPerson.personSex === 1 ? '男' : '女') : '-' }}</li>
<li><span class="person-label">联系方式</span>{{ searchPerson?.phone || '-' }}</li>
<li>
<span class="person-label">类型</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 { uploadPerson } from '@/utils/upload'
export default {
props: {
isDiaglogFaceSearch: { type: Boolean, default: false }
},
data() {
return {
faceSearchVisible: false,
file: null,
fileNames: '',
imageUrl: null,
@ -65,18 +69,16 @@ export default {
const typeItem = typeMap.find(item => item.value === typeValue)
return typeItem ? typeItem.label : '-'
},
//
getPersonTypeTagType(typeValue) {
// type
switch (typeValue) {
case 0:
return '' // -
return ''
case 1:
return 'success' // VIP - 绿
return 'success'
case 2:
return 'info' // -
return 'info'
default:
return '' //
return ''
}
},
@ -87,9 +89,7 @@ export default {
this.file = e.target.files[0]
this.fileNames = this.file.name
// loading
this.isLoading = true
//
this.searchPerson = {}
try {
@ -100,7 +100,6 @@ export default {
'libcode': this.user.fonds.fondsNo
}
//
const res = await uploadPerson(this.baseApi + '/api/person/findPersonFace', this.file, params)
if (res.data.code === 200) {
@ -117,7 +116,6 @@ export default {
console.error('人脸查询出错:', error)
this.$message({ message: '查询过程出错,请重试', type: 'error', offset: 8 })
} finally {
// loading
this.isLoading = false
}
} else {
@ -125,10 +123,8 @@ export default {
this.imageUrl = null
}
//
e.target.value = ''
},
// base64
getBase64(file) {
const reader = new FileReader()
reader.readAsDataURL(file)
@ -137,18 +133,24 @@ export default {
resolve(reader.result)
}
})
},
handleClose() {
this.faceSearchVisible = false
this.imageUrl = null
this.searchPerson = {}
this.btnLoading = false
}
}
}
</script>
<style lang="scss" scoped>
.setting-dialog{
display: flex;
justify-content: flex-start;
}
.row-container{
display: flex;
justify-content: flex-start;
height: calc(100vh - 145px);
padding: 40px 20px !important;
}
.upload-img-input{
position: relative;
width: 150px;
@ -189,15 +191,11 @@ export default {
margin-left: 20px;
padding-left: 10px;
border-left: 1px solid #EBEEF5;
display: flex;
flex-wrap: wrap;
li{
width: 50%;
line-height: 20px;
font-size: 14px;
font-weight: bold;
color: #0c0e1e;
span{
span.person-label{
display: inline-block;
width: 70px;
text-align: right;
@ -206,8 +204,69 @@ export default {
}
}
}
.setting-dialog{
.face-result-info{
display: flex;
flex-wrap: wrap;
li{
width: 50%;
}
}
}
.row-container{
.upload-img-input{
width: 300px;
height: 380px;
& input{
width: 300px;
height: 380px;
}
.upload-libImg{
width: 300px;
height: 380px;
& img{
height: 340px;
}
}
}
.face-result-info{
flex: 1;
display: flex;
flex-direction: column;
margin-left: 30px;
padding-left: 50px;
li{
font-size: 16px;
line-height: 40px;
span.person-label{
width: 100px;
}
}
}
}
::v-deep .el-tag{
width: auto !important;
margin: 0 !important;
border-color: #eef5fe !important;
}
::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;
}
}
</style>

44
src/views/faceRecognition/module/selfRegister.vue

@ -1,37 +1,40 @@
<template>
<el-dialog :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="自助注册" :visible.sync="selfRegisterVisible">
<div class="setting-dialog">
<a
:href="link"
target="_blank"
rel="noopener noreferrer"
class="external-link"
>
{{ link }}
</a>
<div class="qrcode-wrapper">
<div ref="qrcode" class="qrcode" />
</div>
<div :class=" isDialogFaceRegsiter ? 'setting-dialog': 'app-container row-container'">
<a
:href="link"
target="_blank"
rel="noopener noreferrer"
class="external-link"
>
{{ link }}
</a>
<div class="qrcode-wrapper">
<div ref="qrcode" class="qrcode" />
</div>
</el-dialog>
</div>
</template>
<script>
import Qrcode from 'qrcodejs2'
export default {
props: {
isDialogFaceRegsiter: { type: Boolean, default: false }
},
data() {
return {
selfRegisterVisible: false,
link: 'http://192.168.99.72:8080/selfhelp/initReaderCheck.do?strLibcode=FTZN'
}
},
mounted() {
this.generateQrcode()
},
methods: {
generateQrcode() {
const qrcodeRef = this.$refs.qrcode
if (!qrcodeRef) return
qrcodeRef.innerHTML = ''
new Qrcode(qrcodeRef, {
text: this.link,
width: 200,
@ -40,10 +43,6 @@ export default {
colorLight: '#fff',
correctLevel: Qrcode.CorrectLevel.L
})
},
open() {
this.selfRegisterVisible = true
this.$nextTick(() => this.generateQrcode())
}
}
}
@ -69,4 +68,11 @@ export default {
padding: 20px;
}
.row-container{
display: flex;
flex-direction: column;
align-items: center;
height: calc(100vh - 145px);
padding: 40px 20px !important;
}
</style>

37
src/views/faceRecognition/personInfoManage.vue

@ -104,7 +104,7 @@
<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;"> -->
<img v-else :src="baseApi + '/api/fileRelevant/getImg?imgType=3&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>
@ -132,9 +132,16 @@
<pagination v-if="crud.data.length !== 0" />
</div>
<FaceSearch ref="faceSearchRefs" />
<SelfRegister ref="selfRegisterRefs" />
<el-dialog :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="人脸查询" :visible.sync="faceSearchVisible" :before-close="handleClose">
<FaceSearch ref="faceSearchRefs" :is-diaglog-face-search="true" />
</el-dialog>
<BatchImport ref="batchImportRefs" @refresh="updatePerson" />
<el-dialog :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="自助注册" :visible.sync="selfRegisterVisible">
<SelfRegister ref="selfRegisterRefs" :is-dialog-face-regsiter="true" />
</el-dialog>
</div>
</template>
@ -196,7 +203,9 @@ export default {
file: null,
fileNames: '',
filePath: '',
imageUrl: null
imageUrl: null,
selfRegisterVisible: false,
faceSearchVisible: false
}
},
computed: {
@ -243,7 +252,7 @@ export default {
this.$set(crud.form, key, res[key])
})
if (this.form.personPhotoUrl) {
this.imageUrl = this.baseApi + '/api/fileRelevant/getFaceImg?imgId=' + this.form.personPhotoUrl
this.imageUrl = this.baseApi + '/api/fileRelevant/getImg?imgType=3&imgId=' + this.form.personPhotoUrl
} else {
this.imageUrl = require('@/assets/images/user.jpg')
}
@ -285,10 +294,16 @@ export default {
}
},
handleFaceSearch() {
this.$refs.faceSearchRefs.faceSearchVisible = true
this.faceSearchVisible = true
// this.$nextTick(() => {
// this.$refs.faceSearchRefs
// })
},
handleSelfRegister() {
this.$refs.selfRegisterRefs.open()
this.selfRegisterVisible = true
this.$nextTick(() => {
this.$refs.selfRegisterRefs.generateQrcode()
})
},
handleBatchImport() {
this.$refs.batchImportRefs.batchImportVisible = true
@ -370,6 +385,14 @@ export default {
clickRowHandler(row) {
this.$refs.table.clearSelection()
this.$refs.table.toggleRowSelection(row)
},
handleClose() {
this.faceSearchVisible = false
if (this.$refs.faceSearchRefs) {
this.$refs.faceSearchRefs.imageUrl = null
this.$refs.faceSearchRefs.searchPerson = {}
this.$refs.faceSearchRefs.btnLoading = false
}
}
}
}

438
src/views/faceRecognition/personRegister.vue

@ -0,0 +1,438 @@
<template>
<div class="app-container row-container">
<div class="head-container">
<div class="head-search" style="align-items: center; ">
<el-select v-model="query.personType" clearable size="small" placeholder="用户类型" class="filter-item" style="width: 140px;" @change="crud.toQuery">
<i slot="prefix" class="iconfont icon-zhuangtai" />
<el-option v-for="item in userTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-input v-model="query.search" size="small" clearable placeholder="输入证号、姓名、身份证号搜索" prefix-icon="el-icon-search" class="filter-item" style="width: 260px;" @keyup.enter.native="crud.toQuery" />
<rrOperation />
</div>
<crudOperation :permission="permission">
<template v-slot:middle>
<el-button slot="reference" size="mini" :loading="crud.delAllLoading" :disabled="crud.selections.length === 0" @click="toDelete(crud.selections)">
<i class="iconfont icon-shanchu" />
删除
</el-button>
</template>
<template v-slot:right>
<el-button size="mini" @click="handleBatchImport">
<i class="iconfont icon-piliangchengjian" />
批量导入
</el-button>
</template>
</crudOperation>
</div>
<div class="container-wrap">
<span class="right-top-line" />
<span class="left-bottom-line" />
<el-dialog :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title">
<span class="dialog-right-top" />
<span class="dialog-left-bottom" />
<div class="setting-dialog">
<div style="display: flex; justify-content: flex-start;">
<el-form ref="form" :model="form" :rules="rules" size="small" label-width="110px">
<el-form-item label="馆代码" prop="libcode">
<el-input v-model="form.libcode" disabled placeholder="系统自动输入" />
</el-form-item>
<el-form-item label="读者证号" prop="barcode">
<el-input v-model="form.barcode" placeholder="请输入" />
</el-form-item>
<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>
<el-form-item label="联系方式" prop="phone">
<el-input v-model="form.phone" placeholder="请输入" />
</el-form-item>
<el-form-item label="性别" prop="personSex">
<el-radio-group v-model="form.personSex">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="用户类型" prop="personType">
<el-select v-model="form.personType" placeholder="请选择">
<el-option
v-for="(item,index) in userTypeOptions"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<div class="right-user-img">
<img :src="imageUrl || require('@/assets/images/user.jpg')" alt="人员照片">
<div class="upload-btn" style="margin: 0 0 10px 0;">
<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>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确定</el-button>
</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" @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="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="馆代码" 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/getImg?imgType=3&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 :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 :class="scope.row.personType === 1 ? 'remove-vip-btn' : ''" size="mini" style="padding: 5px;" @click="addBlackOrVipUser(scope.row,1)">
<i class="iconfont icon-yooxi" />
{{ scope.row.personType === 1 ? '移出VIP' : '加入VIP' }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-if="crud.data.length !== 0" />
</div>
<el-dialog :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="人脸查询" :visible.sync="faceSearchVisible" :before-close="handleClose">
<FaceSearch ref="faceSearchRefs" :is-diaglog-face-search="true" />
</el-dialog>
<BatchImport ref="batchImportRefs" @refresh="updatePerson" />
<el-dialog :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="自助注册" :visible.sync="selfRegisterVisible">
<SelfRegister ref="selfRegisterRefs" :is-dialog-face-regsiter="true" />
</el-dialog>
</div>
</template>
<script>
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'
import pagination from '@crud/Pagination'
import FaceSearch from './module/faceSearch'
import SelfRegister from './module/selfRegister'
import BatchImport from './module/batchImport'
// import { exportFile } from '@/utils/index'
// import qs from 'qs'
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 }
export default {
name: 'PersonInfoManage',
components: { pagination, crudOperation, rrOperation, FaceSearch, SelfRegister, BatchImport },
cruds() {
return CRUD({ title: '人员信息', idField: 'personId', url: 'api/person/initPersonInfo', crudMethod: { ...crudFace }, optShow: {
add: true,
edit: true,
del: false,
reset: true,
download: false,
group: false
}})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
permission: {
add: ['admin', 'face:add'],
edit: ['admin', 'face:edit'],
del: ['admin', 'face:del']
},
userTypeOptions: [
{ value: 0, label: '普通用户' },
{ value: 1, label: 'VIP用户' },
{ value: 2, label: '黑名单用户' }
],
rules: {
libcode: [
{ required: true, message: '馆代码不可为空', trigger: 'blur' }
],
barcode: [
{ required: true, message: '读者证号不可为空', trigger: 'blur' }
],
personName: [
{ required: true, message: '姓名不可为空', trigger: 'blur' }
],
personType: [
{ required: true, message: '请选择用户类型', trigger: 'change' }
]
},
file: null,
fileNames: '',
filePath: '',
imageUrl: null,
selfRegisterVisible: false,
faceSearchVisible: false
}
},
computed: {
...mapGetters([
'baseApi',
'user'
])
},
created() {
},
mounted() {
},
methods: {
updatePerson() {
this.crud.refresh()
},
[CRUD.HOOK.beforeRefresh]() {
this.crud.query.libcode = this.user.fonds.fondsNo
},
[CRUD.HOOK.afterRefresh]() {
},
//
[CRUD.HOOK.afterToCU](crud, form) {
form.libcode = this.user.fonds.fondsNo
},
//
[CRUD.HOOK.beforeToAdd](crud, form) {
},
//
[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/getImg?imgType=3&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) {
if (crud.form.personPhotoUrl === '' || crud.form.personPhotoUrl === null) {
this.$message({ message: '请选择上传照片', type: 'error', offset: 8 })
return false
}
console.log(crud.form)
return true
},
async changeFile(e) {
const file = e.target.files[0]
if (file && file.type.startsWith('image/')) {
this.file = e.target.files[0]
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.form.personPhotoUrl = res.data.data
}
})
} else {
this.$message({ message: '只可上传图片', type: 'error', offset: 8 })
this.imageUrl = null
}
},
handleFaceSearch() {
this.faceSearchVisible = true
// this.$nextTick(() => {
// this.$refs.faceSearchRefs
// })
},
handleSelfRegister() {
this.selfRegisterVisible = true
this.$nextTick(() => {
this.$refs.selfRegisterRefs.generateQrcode()
})
},
handleBatchImport() {
this.$refs.batchImportRefs.batchImportVisible = true
},
toDelete(datas) {
this.$confirm('此操作将删除当前所选' + this.crud.title + '<span>你是否还要继续?</span>', '提示', {
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true
}).then(() => {
this.crud.delAllLoading = true
const ids = []
datas.forEach(val => {
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)
})
}).catch(() => {
})
},
addBlackOrVipUser(data, type) {
const message = type === 1 ? 'VIP' : '黑名单'
this.$confirm('此操作将所选用户加入 / 移出 ' + message + '<span>你是否还要继续?</span>', '提示', {
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true
}).then(() => {
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 })
})
},
getBase64(file) {
const reader = new FileReader()
reader.readAsDataURL(file)
return new Promise((resolve) => {
reader.onload = () => {
resolve(reader.result)
}
})
},
//
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)
},
handleClose() {
this.faceSearchVisible = false
if (this.$refs.faceSearchRefs) {
this.$refs.faceSearchRefs.imageUrl = null
this.$refs.faceSearchRefs.searchPerson = {}
this.$refs.faceSearchRefs.btnLoading = false
}
}
}
}
</script>
<style lang="scss" scoped>
.face-search.el-button:hover,
.face-search.el-button:focus,
.face-search.el-button--info.is-plain:hover,
.face-search.el-button--info.is-plain:focus{
background: #E8F2FF !important;
}
.right-user-img{
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
margin-left: 30px;
border-left: 1px solid #E8E8E8;
img{
display: block;
width: 150px;
height: 200px;
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