Commit d2c4018b authored by kang.nie@inzymeits.com's avatar kang.nie@inzymeits.com
Browse files

初始化代码

parent de8a8ae5
Pipeline #3105 failed with stages
in 0 seconds
<template>
<div class="photo-of-responsible-person">
<!-- 活体检测规则 -->
<div class="face-recognition-requirement">
<div class="face-recognition-requirement-words">
<p class="tips">
请确保脸部清晰完整,光线充足
</p>
<p class="important-tips">
{{ take ? '拍摄时' : '请使用手机录制,录制时' }}{{ type === LIVENESS_TYPE.ACTION ? '完成以下所要求的动作' : '用普通话大声读如下数字' }}
</p>
<p v-if="type === LIVENESS_TYPE.ACTION" class="action">{{ action }}</p>
<p v-else class="random-num">
<b>{{ number.substr(0,1) }}</b>
<b>{{ number.substr(1,1) }}</b>
<b>{{ number.substr(2,1) }}</b>
<b>{{ number.substr(3,1) }}</b>
</p>
<el-button v-if="take || isTablet" class="btn-take" type="primary" icon="el-icon-video-play" @click="handleTakeVideo">开始验证</el-button>
<p v-else class="tips">
请正对手机屏幕录制2~3秒
</p>
</div>
<div class="face-recognition-requirement-img" :class="take ? 'pc' : ''" />
</div>
<!-- 活体视频上传 -->
<div class="face-recognition-ipload">
<comp-file-info
ref="file"
:limit="1"
:data="data.fileData"
:size="4"
:liveness-type="type"
:random-num="number"
accept=".mp4"
btn-name="上传视频"
label-name="人脸识别视频"
desc="文件类型为视频文件,扩展名要求使用小写字母,允许支持的视频格式包括mp4,小于4MB"
/>
</div>
<DeviceVideo :liveness-type="type" :random-num="number" :show.sync="showDeviceVideo" title="人脸识别验证" @upload-success="handleUploadSuccess" />
</div>
</template>
<script>
import request from '@/utils/request'
import bridge from '@/utils/bridge'
import { getToken } from '@/utils/auth'
import CompFileInfo from '@/components/CompFileInfo'
import DeviceVideo from '@/components/Device/video'
const LIVENESS_TYPE = {
NUMBER: 'LIP',
ACTION: 'ACTION'
}
export default {
name: 'CompFaceRecognition',
components: {
CompFileInfo,
DeviceVideo
},
props: {
// 验证方式:number:读数字 action:动作
type: {
type: String,
default: LIVENESS_TYPE.NUMBER
},
// 是否是摄像
take: {
type: Boolean,
default: false
},
data: {
type: Object,
default: () => ({})
}
},
data() {
return {
// 是否展示录屏弹框
showDeviceVideo: false,
LIVENESS_TYPE,
number: '',
videoUrl: '',
isTablet: window.OS.isTablet
}
},
computed: {
/**
* 当前是否是动作
*/
action() {
// 动作编号
return '请先' + this.number.split(',').map(actionCode => {
try {
return this.$store.getters.dict.livenessActionSeq.find(seq => `${seq.value}` === `${actionCode}`).label
} catch (error) {
console.error(error)
return ''
}
}).join(',再')
}
},
watch: {
data() {
this.updateInternalData()
}
},
created() {
this.init()
},
methods: {
/**
* 开始拍摄视频
*/
handleTakeVideo() {
// 上传文件
const uploadFile = async video => {
// 表单数据
const form = new FormData()
// 遍历数据
form.append('file', video)
// 开始请求数据
const uploadResult = await request({
url: `${process.env.VUE_APP_BASIC_API}file/upload`,
headers: {
'Content-Type': 'multipart/form-data;',
'Authorization': `bearer ${getToken()}`
},
data: form
})
// 上传结果
this.handleUploadSuccess(uploadResult)
}
// 如果当前是平板
if (this.isTablet) {
// 如果当前是一体机
if (this.$store.getters.deviceType === 'aio') {
const callback = (video, type) => {
// 如果当前选择了图片
if (type === 'image') {
this.$message.error('请上传视频文件')
return
}
uploadFile(video)
}
// 获取一体机
bridge.getUvcCameraList((deviceList = []) => {
// 是否有master相机
const slaveCamera = deviceList.find(device => device.productName.toLowerCase().indexOf('slave') >= 0)
// 如果当前有高拍仪的主相机
if (slaveCamera) {
bridge.openTargetUvcCamera(slaveCamera, callback)
} else {
bridge.getUvcCamera(callback)
}
})
} else {
bridge.takeVideo(uploadFile)
}
return
}
// 如果当前是pc,则打开高拍仪
this.showDeviceVideo = true
},
init() {
this.updateInternalData()
},
updateInternalData() {
if (typeof this.data === 'object' && this.data !== null) {
this.number = this.data.number || ''
} else {
this.number = ''
}
},
async validate() {
try {
await this.$refs.file.validate()
return true
} catch (e) {
throw e
}
},
getFormData() {
return this.$refs.file.getFormData()
},
getPendingCacheData() {
return this.$refs.file.getPendingCacheData()
},
/**
* 视频上传成功方法
*/
handleUploadSuccess(data) {
// 当前可访问的路径
data.url = data.accessUrl
// 设置文件名
data.name = '录制的视频.mp4'
// 追加一条数据
this.$refs.file.appendFile(data)
}
}
}
</script>
<style scoped lang="scss">
.photo-of-responsible-person {
overflow: hidden;
.face-recognition-requirement{
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
padding: 10px;
.face-recognition-requirement-img {
width: 490px;
height: 290px;
flex: none;
background: url(../../assets/image/living_body_drawing@2x.png) no-repeat 0 0 / 490px 290px;
&.pc {
background: url(../../assets/image/living_body_drawing_pc.png) no-repeat 0 0 / 490px 290px;
}
}
&-words{
display: flex;
flex-direction: column;
justify-content: space-between;
text-align: center;
color: #666;
font-size: 16px;
.random-num {
display: flex;
justify-content: space-between;
}
.action {
background: #F7F8FA;
border-radius: 8px;
color: #212026;
font-weight: 500;
font-size: 30px;
margin-bottom: 14px;
width: fit-content;
height: 86px;
line-height: 86px;
padding: 0 16px;
white-space: nowrap;
}
b {
width: 56px;
height: 86px;
background: #F7F8FA;
border-radius: 8px;
font-size: 48px;
display: inline-block;
font-weight: bold;
color: #212026;
display: flex;
justify-content: center;
align-items: center;
}
}
&-img{
display: flex;
align-items: center;
img{
width: 100%;
}
}
}
.face-recognition-ipload {
margin-top: 20px;
.face-recognition-video-box{
background: #F7F8FA;
border-radius: 4px;
padding:0 0 20px 20px;
height: 116px;
line-height: 116px;
.el-upload__tip{
display: inline-block;
margin-left: 14px;
font-family: PingFangSC-Regular;
font-size: 12px;
color: rgba(132,133,138,0.70);
line-height: 18px;
font-weight: 400;
}
}
}
.face-recognition-requirement-words{
text-align: left;
margin-left: 78px;
p {
padding: 0;
margin: 0;
}
.btn-take {
width: 108px;
height: 32px;
padding: 0 16px;
margin-top: 36px;
}
.tips{
font-family: PingFangSC-Regular;
font-size: 14px;
color: rgba(132,133,138,0.70);
line-height: 18px;
font-weight: 400;
padding-bottom: 10px;
margin-top: 14px;
}
.important-tips{
margin-bottom: 20px;
font-family: PingFangSC-Medium;
font-size: 18px;
font-weight: 600;
color: #212026;
line-height: 25px;
width: 270px;
}
}
}
</style>
<template>
<div class="comp-file-info">
<el-form ref="formRef" :model="form" :rules="formRules" size="small" label-position="top">
<el-row>
<el-col :span="24">
<el-form-item :label="labelName" prop="fileList">
<el-upload
:limit="limit"
:file-list="form.fileList"
:before-upload="onBeforeUpload"
:before-remove="onUploadBeforeRemove"
:on-success="onUploadSuccess"
:on-exceed="onUploadExceed"
:on-preview="onUploadPreview"
:accept="accept"
:action="uploadParams.action"
:headers="uploadParams.headers"
:data="uploadParams.data"
:multiple="limit > 1"
:class="{'cfi-disable-upload': !enableUploadBtn}"
class="file-uploader tip-nowrap"
>
<template v-if="enableUploadBtn">
<el-button key="1" :icon="btnIcon" size="small" type="primary">{{ btnName }}</el-button>
</template>
<template v-else>
<el-button key="2" :icon="btnIcon" disabled size="small" type="primary">{{ btnName }}</el-button>
</template>
<div slot="tip" class="el-upload__tip">{{ desc }}</div>
</el-upload>
<template #label>
<div class="file-uploader-label">
<span>{{ labelName }}</span>
</div>
</template>
</el-form-item>
</el-col>
</el-row>
</el-form>
<comp-confirm
:show.sync="showErrorConfirm"
:desc="errorConfirmDesc"
:show-cancel-btn="true"
@on-sure="sureDelFile"
/>
<comp-confirm
:show.sync="showErrorTips"
:desc="errorMsg"
/>
<comp-file-preview :show.sync="showFilePreview" :url-list="previewFileList"/>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { getToken } from '@/utils/auth'
import CompConfirm from '@/components/CompConfirm'
import CompFilePreview from '@/components/CompFilePreview'
export default {
name: 'CompFileInfo',
components: {
CompConfirm,
CompFilePreview
},
props: {
data: {
type: Object,
default: () => ({})
},
btnIcon: {
type: String,
default: 'el-icon-upload2'
},
btnName: {
type: String,
default: '上传'
},
labelName: {
type: String,
default: ''
},
limit: {
type: Number,
default: 1
},
accept: {
type: String,
default: '.rar,.zip,.doc,.docx,.pdf,.jpg'
},
size: {
type: Number,
default: 20
},
desc: {
type: String,
default: ''
},
randomNum: {
type: String,
default: ''
},
uploadParams: {
type: Object,
default: () => ({
data: {
rootPath: '',
path: ''
},
headers: {
'Authorization': `bearer ${getToken()}`
},
action: `${process.env.VUE_APP_BASIC_API}file/upload`
})
},
// 验证方式
livenessType: {
type: String,
default: ''
}
},
data() {
const checkFileList = (rule, value, callback) => {
if (!Array.isArray(value) || value.length === 0) {
callback(new Error(`请选择${this.labelName}`))
} else {
callback()
}
}
return {
form: {
fileList: []
},
formRules: {
'fileList': [
{ required: true, validator: checkFileList, trigger: 'change' }
]
},
showDeviceVideo: false,
showErrorConfirm: false,
errorConfirmDesc: '',
showFilePreview: false,
previewFileList: [],
showErrorTips: false,
errorMsg: ''
}
},
computed: {
...mapGetters(['device']),
enableUploadBtn() {
return this.form.fileList.length < this.limit
}
},
watch: {
data() {
this.updateInternalData()
},
'form.fileList'() {
if (this._fileListInitMount) {
this._fileListInitMount = false
return
}
this.validateFileList()
}
},
created() {
this.init()
},
methods: {
errorTips(errMsg) {
this.showErrorConfirm = true
this.errorConfirmDesc = errMsg
},
errorTips2(errorMsg) {
this.showErrorTips = true
this.errorMsg = errorMsg
},
init() {
this._fileListInitMount = true
this.updateInternalData()
},
updateInternalData() {
if (typeof this.data === 'object' && this.data !== null) {
this.form.fileList = this.normalizeFileList(Array.isArray(this.data.fileList) ? [...this.data.fileList] : [])
} else {
this.form.fileList = []
}
},
sureDelFile() {
const delFile = this.form.fileList.filter(file => file.uid === this._curDelFile.uid)[0]
if (delFile) {
this.form.fileList.splice(this.form.fileList.indexOf(delFile), 1)
}
},
makeAcceptReg() {
const arr = this.accept.split(',').filter(item => item !== '').map(item => item.substring(1))
return new RegExp(arr.join('|'), 'i')
},
onUploadExceed() {
this.errorTips2(`上传文件数不能超过${this.limit}`)
},
onBeforeUpload(file) {
const acceptReg = this.makeAcceptReg()
if (!acceptReg.test(file.type)) {
this.errorTips2('上传文件格式不正确')
this._autoRemove = true
return false
}
if (file.size > this.size * 1024 * 1024) {
this.errorTips2(`文件大小不能超过${this.size}M`)
this._autoRemove = true
return false
}
return true
},
onUploadBeforeRemove(file, fileList) {
if (this._autoRemove) {
this._autoRemove = false
return true
}
this.errorTips('确认删除当前文件吗?')
this._curDelFile = file
return false
},
onUploadSuccess(response, file, fileList) {
if (response && response.code === 200) {
if (response.data) {
this.form.fileList = this.normalizeFileList(fileList)
}
}
},
onUploadPreview(file) {
this.showFilePreview = true
this.previewFileList = [{
type: this.getFileTypeFromFileName(file.name),
url: file.url
}]
},
getFileTypeFromFileName(name) {
const suffixIndex = name.indexOf('.')
if (suffixIndex === -1) {
return 'image'
}
const suffix = name.substring(suffixIndex + 1)
if (suffix === 'mp4') {
return 'video'
}
// todo 待续
return 'image'
},
normalizeFileList(fileList = []) {
return fileList.map(file => {
return {
uid: file.uid,
name: file.name,
uuid: file.uuid ? file.uuid : file.response.data.uuid,
url: file.url ? file.url : file.response.data.accessUrl
}
})
},
validateFileList() {
this.$refs.formRef.validateField('fileList')
},
// 校验
async validate() {
try {
await this.$refs.formRef.validate()
return true
} catch (e) {
let temp = {
anchor: () => {
this.$refs.formRef.fields.filter(item => item.validateState === 'error')[0].$el.scrollIntoView(true)
temp = null
}
}
throw temp
}
},
// 获取数据
getFormData() {
return JSON.parse(JSON.stringify(this.form))
},
// 获取待缓存数据
getPendingCacheData() {
return this.getFormData()
},
/**
* 追加一条文件数据
* @param file 文件数据
*/
appendFile(file) {
// 开始在页面上渲染已经上传的文件样式
this.form.fileList = this.normalizeFileList([file])
}
}
}
</script>
<style lang="scss">
.comp-file-info {
.cfi-disable-upload {
.el-upload {
pointer-events: none;
}
}
.file-uploader-label {
display: inline-block;
position: relative;
.icon-device-video {
position: absolute;
left: 100px;
top: 4px;
}
}
}
</style>
<template>
<img v-if="type === 'image'" :src="src" alt="">
<video v-else-if="type === 'video'" controls="controls" preload>
<source :src="src" type="video/mp4">
<p>暂不支持预览</p>
</video>
</template>
<script>
export default {
name: 'File',
props: {
type: {
type: String,
default: 'image'
},
src: {
type: String,
default: ''
}
}
}
</script>
<template>
<transition appear name="cvp-fade" mode="out-in">
<div v-if="show" class="comp-video-preview">
<div class="cvp-mask"/>
<div class="cvp-btn close" @click.stop="onClose">
<i class="el-icon-close"/>
</div>
<div v-if="canNav" class="cvp-btn prev" @click.stop="onPrev">
<i class="el-icon-arrow-left"/>
</div>
<div v-if="canNav" class="cvp-btn next" @click.stop="onNext">
<i class="el-icon-arrow-right"/>
</div>
<div class="cvp-content">
<template v-for="(item, i) in urlList">
<file
v-if="i === index"
:key="item.url"
:type="item.type"
:src="item.url"
class="cvp-file"/>
</template>
</div>
</div>
</transition>
</template>
<script>
import File from './file'
export default {
name: 'VideoPreview',
components: {
File
},
props: {
urlList: {
type: Array,
default: () => ([])
},
show: {
type: Boolean,
default: false
}
},
data() {
return {
index: 0
}
},
computed: {
canNav() {
return Array.isArray(this.urlList) && this.urlList.length > 1
}
},
methods: {
onPrev() {
const len = this.urlList.length
this.index = (this.index - 1 + len) % len
},
onNext() {
const len = this.urlList.length
this.index = (this.index + 1) % len
},
onClose() {
this.$emit('update:show', false)
}
}
}
</script>
<style lang="scss">
.comp-video-preview {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2020;
.cvp-mask {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
opacity: .5;
background: #000;
}
.cvp-content {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.cvp-file {
display: block;
width: 72%;
height: auto;
}
}
.cvp-btn {
position: absolute;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
opacity: .8;
cursor: pointer;
box-sizing: border-box;
user-select: none;
&.close {
top: 40px;
right: 40px;
width: 40px;
height: 40px;
font-size: 24px;
color: #fff;
background-color: #606266;
}
&.prev,
&.next {
top: 50%;
transform: translateY(-50%);
width: 44px;
height: 44px;
font-size: 24px;
color: #fff;
background-color: #606266;
border-color: #fff;
}
&.next {
right: 40px;
text-indent: 2px;
}
}
}
.cvp-fade-appear,
.cvp-fade-enter,
.cvp-fade-leave-to {
opacity: 0;
}
.cvp-fade-appear-active,
.cvp-fade-enter-active,
.cvp-fade-leave-active {
transition: opacity .5s;
}
.cvp-fade-appear-to,
.cvp-fade-enter-to,
.cvp-fade-leave {
opacity: 1;
}
</style>
<template>
<div class="comp-master-file-info">
<el-row :gutter="40">
<el-col :span="12">
<file-info
ref="protocol"
:sign="deviceType === 'pc'"
:limit="4"
:file-size="20"
:data="protocolUrlData"
:upload-type="['camera', 'sign']"
:attachment-url="attachmentUrl"
label="入网协议"
sign-key="peopleAgreement"
placeholder="上传文件"
/>
</el-col>
<el-col v-if="type !== 'new'" :span="12">
<file-info
ref="contract"
:limit="5"
:file-size="20"
:data="contractUrlData"
label="购车合同"
placeholder="上传文件"
/>
</el-col>
<el-col v-else :span="12">
<file-info
ref="notify"
:sign="deviceType === 'pc'"
:limit="4"
:file-size="20"
:data="notifyUrlData"
:upload-type="['camera', 'sign']"
sign-key="realnameNotice"
label="责任告知书"
placeholder="上传文件"
/>
</el-col>
</el-row>
<el-row v-if="type !== 'new'" :gutter="40">
<el-col :span="12">
<file-info
ref="bill"
:limit="5"
:file-size="20"
:data="billUrlData"
label="购车发票或行驶本之一"
placeholder="上传文件"
/>
</el-col>
<el-col :span="12">
<file-info
ref="transfer"
:limit="5"
:file-size="20"
:data="transferUrlData"
label="过户证明"
placeholder="上传文件"
/>
</el-col>
</el-row>
<el-row v-if="type !== 'new'" :gutter="40">
<el-col :span="12">
<file-info
ref="notify"
:sign="deviceType === 'pc'"
:limit="4"
:file-size="20"
:data="notifyUrlData"
:upload-type="['camera', 'sign']"
sign-key="realnameNotice"
label="责任告知书"
placeholder="上传文件"
/>
</el-col>
</el-row>
</div>
</template>
<script>
import FileInfo from '@/components/FileInfo'
import { mapGetters } from 'vuex'
export default {
name: 'CompMasterFileInfo',
components: {
FileInfo
},
props: {
data: {
type: Object,
default: () => ({})
},
type: {
type: String,
default: 'new'
},
attachmentUrl: {
type: String,
default: ''
}
},
data() {
return {
form: {
protocolUrl: [],
contractUrl: [],
billUrl: [],
transferUrl: [],
notifyUrlData: []
}
}
},
computed: {
...mapGetters(['deviceType']),
outerData() {
return this.data || {}
},
protocolUrlData() {
return {
fileList: this.outerData.protocolUrl || []
}
},
contractUrlData() {
return {
fileList: this.outerData.contractUrl || []
}
},
billUrlData() {
return {
fileList: this.outerData.billUrl || []
}
},
transferUrlData() {
return {
fileList: this.outerData.transferUrl || []
}
},
notifyUrlData() {
return {
fileList: this.outerData.notifyUrlData || []
}
}
},
methods: {
// 校验
async validate() {
let validateList = null
if (this.type === 'new') {
validateList = [
this.$refs.protocol.validate(),
this.$refs.notify.validate()
]
} else {
validateList = [
this.$refs.protocol.validate(),
this.$refs.contract.validate(),
this.$refs.bill.validate(),
this.$refs.transfer.validate(),
this.$refs.notify.validate()
]
}
try {
await Promise.all(validateList)
return true
} catch (e) {
throw e
}
},
// 获取数据
getFormData() {
if (this.type === 'new') {
const protocolUrl = this.$refs.protocol.getPendingCacheData().fileList
const notifyUrl = this.$refs.notify.getPendingCacheData().fileList
return {
protocolUrl,
notifyUrl
}
} else {
const protocolUrl = this.$refs.protocol.getPendingCacheData().fileList
const contractUrl = this.$refs.contract.getPendingCacheData().fileList
const billUrl = this.$refs.bill.getPendingCacheData().fileList
const transferUrl = this.$refs.transfer.getPendingCacheData().fileList
const notifyUrl = this.$refs.notify.getPendingCacheData().fileList
return {
protocolUrl,
contractUrl,
billUrl,
transferUrl,
notifyUrl
}
}
},
// 获取待缓存数据
getPendingCacheData() {
return this.getFormData()
}
}
}
</script>
<style lang="scss">
.comp-master-file-info {
}
</style>
<script>
import bridge, { isInApp } from '@/utils/bridge'
export default {
name: 'CompScanCode',
props: {
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
isInApp
}
},
methods: {
/**
* 开始扫码
* @param isBarcode 是否是条形码
*/
handleTakeScan(isBarcode) {
bridge.scanCode(isBarcode, code => {
this.$emit('change', code)
})
}
},
render() {
const defaultSlot = this.$scopedSlots.default()
// 如果当前是app内部
if (this.isInApp) {
return (
<el-popover
disabled={this.disabled}
class='component-scan-code'
placement='bottom'
width='280'
trigger='click'
scopedSlots={{
reference: () => defaultSlot
}}
>
<div class='operation-wrapper'>
<el-button disabled={this.disabled} icon='el-icon-camera' onClick={this.handleTakeScan.bind(this, false)}>扫描二维码</el-button>
<el-button disabled={this.disabled} icon='el-icon-document' type='primary' onClick={this.handleTakeScan.bind(this, true)}>扫描条形码</el-button>
</div>
</el-popover>
)
}
return defaultSlot
}
}
</script>
<style lang="scss">
.component-scan-code {
.operation-wrapper {
align-items: center;
display: flex;
justify-content: space-between;
}
}
</style>
<template>
<el-dialog
:visible.sync="showDialog"
:close-on-press-escape="false"
:close-on-click-modal="false"
class="comp-sign rnr-dialog"
width="750px"
top="10vh"
title="签名"
@close="closeDialog"
>
<div class="comp-sign-container">
<el-row class="agreement-wrapper">
<img :src="originUrl" crossorigin="anonymous" class="preview-img" @click="handleShowPreview" >
<img :src="attachmentUrl" class="preview-img" @click="handleShowPreview">
<el-button type="primary" icon="el-icon-refresh-left" @click="handleClearSign">重签</el-button>
</el-row>
<SignCanvas ref="signRef" v-model="sign" :options="options" class="sign-area" />
<image-viewer v-if="showViewer" :z-index="2010" :on-close="handleCloseViewer" :url-list="urlList" />
<div slot="footer" class="footer">
<el-button @click="closeDialog">取消</el-button>
<el-button :loading="composeLoading" type="primary" @click="handleComposeSign">完成</el-button>
</div>
<div :id="paintAreaId" class="paint-wrapper">
<img :src="originUrl" crossorigin="anonymous" class="paint-image">
<img :src="sign" class="paint-sign-image">
<img v-if="attachmentUrl" :src="attachmentUrl" class="paint-image">
</div>
</div>
</el-dialog>
</template>
<script>
import SignCanvas from 'sign-canvas'
import ImageViewer from '@/components/Uploader/image-viewer'
import '@/styles/dialog.scss'
import html2canvas from 'html2canvas'
export default {
name: 'CompSign',
components: { SignCanvas, ImageViewer },
props: {
show: {
type: Boolean,
default: false
},
originUrl: {
type: String,
default: ''
},
attachmentUrl: {
type: String,
default: ''
}
},
data() {
return {
// 签字内容
sign: '',
// 是否展示dialog
showDialog: false,
// 签名组件需要的属性
options: {
// 书写速度 [Number] 可选
lastWriteSpeed: 1,
// 下笔的宽度 [Number] 可选
lastWriteWidth: 4,
// 线条的边缘类型 [butt]平直的边缘 [round]圆形线帽 [square] 正方形线帽
lineCap: 'round',
// 线条交汇时边角的类型 [bevel]创建斜角 [round]创建圆角 [miter]创建尖角。
lineJoin: 'round',
// canvas宽高 [Number] 可选
canvasWidth: 700,
// 高度 [Number] 可选
canvasHeight: 300,
// 是否显示边框 [可选]
isShowBorder: false,
// 背景色 [String] 可选
bgColor: '#FFFFFF',
// 网格线宽度 [Number] 可选
// borderWidth: 1,
// 网格颜色 [String] 可选
// borderColor: "#ff787f",
// 基础轨迹宽度 [Number] 可选
writeWidth: 5,
// 写字模式最大线宽 [Number] 可选
maxWriteWidth: 30,
// 写字模式最小线宽 [Number] 可选
minWriteWidth: 5,
// 轨迹颜色 [String] 可选
writeColor: '#101010',
// 签名模式 [Boolean] 默认为非签名模式,有线框, 当设置为true的时候没有任何线框
isSign: true,
// 下载的图片格式 [String] 可选为 jpeg canvas本是透明背景的
imgType: 'png'
},
// 组合签名的loading
composeLoading: false,
// 预览组件的图片链接
urlList: [],
// 是否展示文件预览器
showViewer: false,
// 合成后的协议文件
agreement: this.originUrl,
// 绘图区域id
paintAreaId: `paint-${Date.now()}${Math.floor(Math.random() * 1000)}`
}
},
watch: {
show(val) {
// 将内外状态同步
this.showDialog = val
}
},
methods: {
/**
* 预览照片
*/
handleShowPreview() {
// 展示预览组件
this.showViewer = true
// 预览组件中url的列表
this.urlList = this.agreement ? [this.agreement, this.attachmentUrl] : [this.originUrl, this.attachmentUrl]
},
/**
* 关于预览功能
*/
handleCloseViewer() {
this.showViewer = false
},
/**
* 重新签名
*/
handleClearSign() {
this.$refs.signRef.canvasClear()
},
/**
* 将协议和签名组合在一起
*/
async handleComposeSign() {
// 展示loading框
this.composeLoading = true
try {
const cvs = await html2canvas(
document.querySelector(`#${this.paintAreaId}`),
{ useCORS: true }
)
// 将合并后的图片转换成文件
return new Promise((resolve, reject) => {
cvs.toBlob(blob => {
// 关闭loading状态
this.composeLoading = false
// 将生成的图片传递给父组件
this.$emit('change', new File([blob], `${Date.now()}.png`, { type: 'image/png' }))
// 清空签名
this.handleClearSign()
// 返回处理结果
resolve()
})
})
} catch (error) {
console.log('合成图片发生错误:', error)
}
// 关闭loading状态
this.composeLoading = false
},
/**
* 关闭弹框
*/
closeDialog() {
this.showDialog = false
this.$emit('close', false)
}
}
}
</script>
<style lang="scss">
.comp-sign {
.el-row {
margin-top: 0;
}
.agreement-wrapper {
overflow: hidden;
.preview-img {
width: auto;
height: 68px;
}
.el-button {
float: right;
margin-top: 18px;
}
}
.sign-area {
border: 1px dashed;
margin-top: 12px;
}
.footer {
text-align: right;
margin-top: 12px;
}
.paint-wrapper {
background-color: #ffffff;
width: 600px;
position: fixed;
left: 100vw;
top: 100vh;
.paint-image {
width: 100%;
height: auto;
}
.paint-sign-image {
width: 160px;
height: auto;
margin: -300px 0 0 400px;
}
}
}
</style>
<template>
<el-form class="component-vin-input" label-position="top">
<el-form-item label="VIN码" :required="true">
<template #label>
<span>VIN码</span>
<el-tooltip class="item" effect="dark" content="不可提交属于其他责任人的VIN码" placement="top-start">
<i class="el-icon-warning-outline" />
</el-tooltip>
</template>
<div class="vin-input-wrapper">
<div v-for="(vin, index) in vinGroup" :key="index" :class="vin.result ? '' : 'input-error'" class="vin-edit">
<el-input v-model="vin.vin" maxlength="17" @input="value => handleVinItemChange(value, index)" @blur="validate([], index)" />
<div class="error-tip">{{ vin.message }}</div>
</div>
<el-input v-model="vinInput" class="vin-input" placeholder="请输入或粘贴VIN码" @input="handleVinInput" @blur="handleVinInputBlur" />
</div>
<div class="vin-tip">*每个VIN码输入完成后,以分号(;)结束</div>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: 'CompVin',
props: {
value: {
type: Array,
default: () => ([])
}
},
data() {
return {
vinGroup: [],
vinInput: ''
}
},
mounted() {
this.value.forEach(vin => {
this.vinGroup.push({
result: true,
vin,
message: ''
})
})
},
methods: {
/**
* 更新vin的页面显示
* @param vinList vin的列表
*/
updateVinGroup(vinList) {
// 先清空vin
this.vinGroup = []
// 遍历列表数据
vinList.forEach(vin => {
this.vinGroup.push({
result: true,
vin,
message: ''
})
})
},
/**
* vin改变的方法,将其格式化成数组
*/
handleVinInput() {
// 如果当前没有输入
if (!this.vinInput) {
return
}
// 替换中文的分号
const replaceChinessSemicolon = this.vinInput.replace(/[;,,]/g, ';')
// 按逗号分割
const vinList = replaceChinessSemicolon.split(';')
// 取出最后一个当成输入项目
this.vinInput = vinList.pop()
// 重新赋值vin的集合
this.vinList = [
...(this.vinList || []),
...vinList.filter(vin => !!vin)
]
// 先清空之前的数据
this.vinGroup = [
...this.vinGroup,
...vinList.filter(vin => !!vin).map(vin => ({
result: vin.length === 17,
vin,
message: vin.length === 17 ? '' : '请输入合法的vin号'
}))
]
// 双向绑定vinList
this.$emit('input', this.vinList)
},
/**
* vin输入框失去焦点的方法
*/
handleVinInputBlur() {
// 给vin加上;
this.vinInput = this.vinInput + ';'
// 将当前输入的vin初始化成需要校验的参数
this.handleVinInput()
},
/**
* vin输入的改变方法
*/
handleVinItemChange(value, index) {
// 如果当前没有输入值
if (!value) {
this.vinGroup.splice(index, 1)
} else {
// 替换中文的分号
const replaceChinessSemicolon = value.replace(/[;,,]/g, ';')
// 按逗号分割
const vinList = replaceChinessSemicolon.split(';').filter(vin => !!vin)
// 如果当前长度大于1个,说明输入了;
if (vinList.length >= 1) {
// 将当前输入的数据插到原来的对象中
vinList.forEach((vin, vIndex) => {
this.vinGroup.splice(index + vIndex, vIndex === 0 ? 1 : 0, {
result: vin.length === 17,
vin,
message: vin.length === 17 ? '' : '请输入合法的vin号'
})
})
}
}
// 重新赋值
this.vinList = this.vinGroup.map(vin => vin.vin)
// 双向绑定vinList
this.$emit('input', this.vinList)
},
/**
* 校验方法
* @param results 需要回显的校验结果
* @param index 当前需要校验的项目
*/
validate(results = [], index = -1) {
// 当前是否是错误
let isError = true
// 当前vin的集合
const vinList = []
// 如果没有输入
if (this.vinGroup.length === 0) {
this.$message.error('请输入VIN码')
throw new Error('请输入VIN码')
}
// 写入错误信息
this.vinGroup.forEach((group, gIndex) => {
// 如果当前传入了需要校验的项目,则直接校验,如果没传,则全量校验
if (index === -1 || gIndex === index) {
// 如果当前vin输入的不合法
if (group.vin.length !== 17) {
isError = false
group.result = false
group.message = '请输入合法的vin号'
} else {
group.result = true
group.message = ''
}
// 现在开始校验输入的结果
results.forEach(result => {
if (group.vin === result.vin) {
group.result = result.checkResult
group.message = result.errorMsg
isError = isError && result.checkResult
}
})
vinList.push(group.vin)
}
})
// 如果校验有错误
if (!isError) {
throw new Error('请输入正确的VIN号')
}
return vinList
}
}
}
</script>
<style lang="scss">
.component-vin-input {
.el-textarea {
width: 896px;
height: 390px;
.el-textarea__inner {
padding: 12px;
}
}
.vin-input-wrapper {
border: 1px solid #DCDFE6;
border-radius: 4px;
width: 896px;
min-height: 390px;
.vin-edit {
display: inline-block;
width: 168px;
margin-left: 8px;
margin-top: 8px;
.el-input {
width: 100%;
height: 36px;
.el-input__inner {
background-color: #F7F8FA;
border-color: #ffffff;
box-shadow: none;
color: #212026;
font-size: 14px;
font-weight: 400;
padding: 0 10px;
}
}
.error-tip {
display: none;
color: #FF4D4F;
font-weight: 400;
font-size: 12px;
line-height: 18px;
height: 18px;
margin-top: 2px;
position: absolute;
}
&.input-error {
margin-bottom: 20px;
.el-input {
.el-input__inner {
border-color: #FF4D4F;
}
}
.error-tip {
display: block;
}
}
}
.vin-input {
display: inline-block;
width: 168px;
margin-left: 8px;
margin-top: 8px;
.el-input__inner {
background-color: #FFFFFF;
border-color: #ffffff;
box-shadow: none;
color: #212026;
font-size: 14px;
font-weight: 400;
padding: 0 10px;
}
}
}
.vin-tip {
color: rgba(132, 133, 138, 0.7);
font-size: 12px;
font-weight: 400;
line-height: 18px;
height: 18px;
margin-top: 10px;
}
}
</style>
<template>
<div class="comp-car-card">
<el-form ref="formRef" :model="form" :rules="formRules" size="small" label-position="top" @submit.native.prevent>
<el-row>
<el-col :span="24">
<div class="add-vin-btn-wrapper">
<el-form-item label="VIN列表" prop="vinList">
<el-button :disabled="!enableAddBtn" class="ccc-add-vin-btn" type="primary" icon="el-icon-plus" plain size="small" @click.stop="clickAddVin">添加VIN码</el-button>
<!-- <el-button v-if="isInApp" :disabled="!enableAddBtn" class="ccc-add-vin-btn" type="primary" icon="el-icon-camera" plain size="small" @click.stop="handleTakeScan">扫码添加</el-button> -->
</el-form-item>
</div>
<div class="vin-table-wrapper">
<el-table
v-loading="loading"
:data="vinList"
style="width: 100%">
<el-table-column
type="index"
label="序号"
width="180"/>
<el-table-column
prop="vin"
label="VIN"
min-width="320"
>
<template slot-scope="scope">
<template v-if="scope.row.state === 'readonly'">{{ scope.row.vin }}</template>
<template v-else-if="scope.row.state === 'edit'">
<el-form :ref="scope.row._formId" :model="scope.row" :rules="vinRules" size="small" label-width="0" @submit.native.prevent>
<el-form-item prop="vin" class="ccc-el-form-item" :error="scope.row.error">
<el-input :ref="scope.row._inputId" :value="scope.row.vin" clearable maxlength="50" class="ccc-vin-el-input" @input="onVinInput($event, scope.row)"/>
</el-form-item>
</el-form>
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-link v-if="canDelVinRecord(scope)" class="ccc-el-link-del" @click.stop="clickVinItemDel(scope)">删除</el-link>
</template>
</el-table-column>
</el-table>
</div>
</el-col>
</el-row>
</el-form>
<comp-confirm
:show.sync="showDelConfirm"
:desc="delConfirmDesc"
:show-cancel-btn="true"
@on-sure="onDelSure"
/>
<comp-confirm
:show.sync="showTipConfirm"
:desc="tipConfirmDesc"
/>
</div>
</template>
<script>
import bridge, { isInApp } from '@/utils/bridge'
import { checkVin } from '@/api/iccid'
import { queryUnBindCardByVin, queryBindCardByVin } from '@/api/iccid'
import CompConfirm from '@/components/CompConfirm'
import CompScanCode from '@/components/CompScanCode'
let gid = 0
export default {
name: 'CompCarCard',
components: {
CompConfirm,
CompScanCode
},
props: {
data: {
type: Object,
default: () => ({})
},
maxVinLength: {
type: Number,
default: 30
},
// 特殊业务卡解绑重新封装依据vin获取iccid等信息 SPECIAL
soucePage: {
type: String,
default: () => ''
},
// 是否查询绑定的iccid true:已绑定 false:未绑定
isBind: {
type: Boolean,
default: false
}
},
data() {
const checkVin = (rule, value, callback) => {
if (value !== '') {
if (this.checkVin(value)) {
callback()
} else {
callback(new Error('VIN码格式不正确'))
}
} else {
if (this.validVinList.length) {
callback()
} else {
callback(new Error('请输入VIN码'))
}
}
}
return {
form: {
vin: '',
vinList: []
},
formRules: {
vinList: [
{ type: 'array', required: true, message: '请添加VIN码数据', trigger: '' }
]
},
vinRules: {
vin: [
{ required: true, validator: checkVin, trigger: 'blur' }
]
},
isInApp,
vinList: [],
loading: false,
showDelConfirm: false,
delConfirmDesc: '确认删除当前行吗?',
showTipConfirm: false,
tipConfirmDesc: '',
inputIsFocus: false
}
},
computed: {
newAddedVinList() {
return this.vinList.filter(item => item.state === 'edit')
},
validVinList() {
return this.vinList.filter(item => (item.state === 'edit' ? this.checkVin(item.vin) : (item.vin !== '')))
},
enableAddBtn() {
const ret = (!this.inputIsFocus && !this.loading) || (this.inputIsFocus && !this.form.vin)
this.$emit('on-canenable', ret)
return ret
}
},
watch: {
data() {
this.updateInternalData()
},
vinList(val) {
if (this._vinListInitMount) {
this._vinListInitMount = false
return
}
if (val.length) {
this.$refs.formRef.clearValidate('vinList')
} else {
this.$refs.formRef.validateField('vinList')
}
}
},
created() {
this._vinListInitMount = true
this.init()
},
methods: {
errorTips(errMsg) {
this.showTipConfirm = true
this.tipConfirmDesc = errMsg
},
init() {
this.updateInternalData()
},
updateInternalData() {
if (typeof this.data === 'object' && this.data !== null) {
this.form.vinList = []
this.vinList = Array.isArray(this.data.vinList) ? [...this.data.vinList] : this.getDefaultVinList()
} else {
this.form.vinList = []
this.vinList = this.getDefaultVinList()
}
},
getDefaultVinList() {
return []
},
// 点击添加 ICCID 按钮
clickAddVin() {
if (this.vinList.length >= this.maxVinLength) {
this.errorTips(`最多只能添加${this.maxVinLength}条ICCID数据`)
return
}
const newVin = this.makeVinTemplate()
newVin.state = 'edit'
this.vinList.push(newVin)
this.$nextTick(() => {
this.$refs[`${newVin._inputId}`].focus()
})
},
// 点击删除 Vin 记录按钮
clickVinItemDel(scope) {
this.showDelConfirm = true
this._curDelIndex = scope.$index
},
// 确认删除 ICCID
onDelSure() {
this.vinList.splice(this._curDelIndex, 1)
// 页面渲染完成后检验一下数据
this.$nextTick(() => {
this.validateVinListForm2()
})
},
canDelVinRecord(scope) {
// 卡解绑查询出来的数据不能删除
if (this.$route.name === 'CardUnbindingStepOne') {
if (scope.row.state === 'readonly') {
return false
}
}
return true
},
onVinFocus() {
this.inputIsFocus = true
},
// 光标移开 vin 码输入框时的动作
onVinBlur() {
this.inputIsFocus = false
this.$refs.formRef.validateField('vin', errorMsg => {
if (!errorMsg) {
this.onVinBlurHandler()
} else {
if (this._req) {
this._req.cancel()
}
}
})
},
// 光标移开 vin 码输入框处理
async onVinBlurHandler() {
this.loading = true
const retData = await this.getVinListByVin()
this.loading = false
if (retData === 'cancel') {
return
}
const tempVinList = this.vinList.map(item => item.vin)
const pendingAdd = (retData.vinList || []).filter(item => tempVinList.indexOf(item) === -1)
const addCount = this.maxVinLength - this.vinList.length
if (this._preVin === this.form.vin) {
this.vinList = [
...this.normalizeVinList(pendingAdd.slice(0, addCount)),
...this.vinList
]
} else {
this.vinList = [
...this.normalizeVinList(pendingAdd.slice(0, addCount)),
...this.vinList.filter(item => item.state !== 'readonly')
]
}
this._preVin = this.form.vin
},
// vin 码输入长度限制
onVinInput($event, cur) {
if ($event === '' || $event.length <= 17) {
cur.vin = $event
}
},
checkVin(vin) {
return vin.length === 17
},
async getVinListByVin() {
let retData = null
const searchParams = {
vin: this.form.vin
}
try {
// 接口请求方法
const reqFuction = this.isBind ? queryBindCardByVin : queryUnBindCardByVin
// 开始请求接口
const respData = await reqFuction({
data: {
...searchParams
},
cancelToken: this._req.token,
hideToast: true
})
retData = respData || {}
} catch (e) {
if (e.code === '_CANCEL_REQUEST_') {
retData = 'cancel'
} else {
// 其他错误需要提示
this.errorTips(e.message || '系统异常')
}
}
return retData || {}
},
// ICCID 记录数据模板
makeVinTemplate() {
return {
vin: '',
state: 'readonly',
_formId: gid++,
_inputId: gid++
}
},
// 标准化远程获取的 ICCID 记录数据
normalizeVinList(list = []) {
return list.map(item => {
const newVin = this.makeVinTemplate()
newVin.vin = item
newVin.state = 'readonly'
return newVin
})
},
async validateForm1(isErrorAnchor = false) {
this.form.vinList = JSON.parse(JSON.stringify(this.vinList))
try {
await this.$refs.formRef.validate()
return true
} catch (e) {
if (isErrorAnchor) {
this.$refs.formRef.fields.filter(item => item.validateState === 'error')[0].$el.scrollIntoView(true)
}
let temp = {
anchor: () => {
this.$refs.formRef.fields.filter(item => item.validateState === 'error')[0].$el.scrollIntoView(true)
temp = null
}
}
throw temp
}
},
async validateVinListForm2(isErrorAnchor = false) {
const validateVinList = this.newAddedVinList
for (let i = 0, len = validateVinList.length; i < len; i++) {
const item = validateVinList[i]
try {
await this.$refs[item._formId].validate()
} catch (e) {
if (isErrorAnchor) {
this.$refs[item._inputId].$el.scrollIntoView(true)
}
let temp = {
anchor: () => {
this.$refs[item._inputId].$el.scrollIntoView(true)
temp = null
}
}
throw temp
}
}
return true
},
// 校验
async validate(isErrorAnchor = false) {
try {
await Promise.all([
this.validateForm1(),
this.validateVinListForm2()
])
// 校验输入结果
await this.checkList()
return true
} catch (e) {
throw e
}
},
/**
* 接口校验vin的有效性
*/
async checkList() {
this.vinList.forEach(vin => {
this.$set(vin, 'error', '')
})
// iccid的集合
this.iccidList = []
// 获取校验的数据
const { checkList } = await checkVin({ vinList: this.vinList.map(vin => vin.vin) })
// 如果当前校验失败
if (checkList.some(item => !item.checkResult)) {
checkList.forEach(check => {
this.vinList.forEach(vin => {
if (check.vin === vin.vin && vin.error !== check.errorMsg) {
this.$set(vin, 'error', check.checkResult ? '' : check.errorMsg)
}
})
})
throw new Error('接口校验失败')
}
// 校验项目
checkList.forEach(check => {
this.iccidList.push({
vin: check.vin,
iccidList: check.iccidList
})
})
},
// 获取数据
getFormData() {
return {
vinList: this.vinList.map(item => item.vin)
}
},
// 获取待缓存数据
getPendingCacheData() {
return {
vinList: JSON.parse(JSON.stringify(this.vinList)),
iccidList: JSON.parse(JSON.stringify(this.iccidList || []))
}
},
/**
* 扫码添加
*/
handleScanCallback(code) {
if (this.vinList.length >= this.maxVinLength) {
this.errorTips(`最多只能添加${this.maxVinLength}条ICCID数据`)
return
}
const newVin = this.makeVinTemplate()
newVin.state = 'edit'
newVin.vin = code
this.vinList.push(newVin)
},
/**
* 扫码功能
*/
handleTakeScan() {
bridge.scanCode(true, code => {
this.handleScanCallback(code)
})
}
}
}
</script>
<style lang="scss">
.comp-car-card {
.ccc-vin-input {
width: 44%;
}
.ccc-vin-el-input {
width: 60%;
}
.ccc-el-form-item {
.el-form-item__error {
left: 62%;
top: 50%;
transform: translateY(-50%);
}
}
.add-vin-btn-wrapper {
margin-bottom: 8px;
.ccc-add-vin-btn {
border-color: #B9CFFF;
color: #2A68FF;
background: #F5F7FF;
&:hover {
border-color: #5489FF;
background: #F5F7FF;
color: #5489FF;
}
}
}
.vin-table-wrapper {
.el-form-item {
margin-bottom: 0 !important;
}
.ccc-el-link-del {
font-size: 14px;
color: #2A68FF;
&:hover {
&:after {
display: none;
}
}
}
}
}
</style>
/* eslint-disable */
// Blob 工具类
/**
* Create a blob builder even when vendor prefixes exist
*/
var BlobBuilder = global.BlobBuilder
|| global.WebKitBlobBuilder
|| global.MSBlobBuilder
|| global.MozBlobBuilder;
/**
* Check if Blob constructor is supported
*/
var blobSupported = (function() {
try {
var a = new Blob(['hi']);
return a.size === 2;
} catch(e) {
return false;
}
})();
/**
* Check if Blob constructor supports ArrayBufferViews
* Fails in Safari 6, so we need to map to ArrayBuffers there.
*/
var blobSupportsArrayBufferView = blobSupported && (function() {
try {
var b = new Blob([new Uint8Array([1,2])]);
return b.size === 2;
} catch(e) {
return false;
}
})();
/**
* Check if BlobBuilder is supported
*/
var blobBuilderSupported = BlobBuilder
&& BlobBuilder.prototype.append
&& BlobBuilder.prototype.getBlob;
/**
* Helper function that maps ArrayBufferViews to ArrayBuffers
* Used by BlobBuilder constructor and old browsers that didn't
* support it in the Blob constructor.
*/
function mapArrayBufferViews(ary) {
for (var i = 0; i < ary.length; i++) {
var chunk = ary[i];
if (chunk.buffer instanceof ArrayBuffer) {
var buf = chunk.buffer;
// if this is a subarray, make a copy so we only
// include the subarray region from the underlying buffer
if (chunk.byteLength !== buf.byteLength) {
var copy = new Uint8Array(chunk.byteLength);
copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength));
buf = copy.buffer;
}
ary[i] = buf;
}
}
}
function BlobBuilderConstructor(ary, options) {
options = options || {};
var bb = new BlobBuilder();
mapArrayBufferViews(ary);
for (var i = 0; i < ary.length; i++) {
bb.append(ary[i]);
}
return (options.type) ? bb.getBlob(options.type) : bb.getBlob();
};
function BlobConstructor(ary, options) {
!blobSupportsArrayBufferView && mapArrayBufferViews(ary);
return new Blob(ary, options || {});
};
var BlobHook = (function() {
if (blobSupported) {
return BlobConstructor;
} else if (blobBuilderSupported) {
return BlobBuilderConstructor;
} else {
return null;
}
})();
// 转换 base64 字符串到二进制字符串
function base64ToBinaryString(b64s) {
return window.atob(b64s.split(',')[1]);
}
// 转换 base64 字符串到字节数组
function base64ToArrayBuffer(b64s) {
var binaryString = base64ToBinaryString(b64s);
var len = binaryString.length;
var buf = new ArrayBuffer(len);
var view = new Uint8Array(buf);
for (var i = 0; i < len; i++) {
view[i] = binaryString.charCodeAt(i);
}
return view;
}
export default {
Blob: BlobHook,
// 转换 base64 字符串到二进制字符串
base64ToBinaryString: base64ToBinaryString,
// 转换 base64 字符串到字节数组
base64ToArrayBuffer: base64ToArrayBuffer
};
/* eslint-disable */
var debug = false;
var EXIF = function(obj) {
if (obj instanceof EXIF) return obj;
if (!(this instanceof EXIF)) return new EXIF(obj);
this.EXIFwrapped = obj;
};
var ExifTags = EXIF.Tags = {
// version tags
0x9000 : "ExifVersion", // EXIF version
0xA000 : "FlashpixVersion", // Flashpix format version
// colorspace tags
0xA001 : "ColorSpace", // Color space information tag
// image configuration
0xA002 : "PixelXDimension", // Valid width of meaningful image
0xA003 : "PixelYDimension", // Valid height of meaningful image
0x9101 : "ComponentsConfiguration", // Information about channels
0x9102 : "CompressedBitsPerPixel", // Compressed bits per pixel
// user information
0x927C : "MakerNote", // Any desired information written by the manufacturer
0x9286 : "UserComment", // Comments by user
// related file
0xA004 : "RelatedSoundFile", // Name of related sound file
// date and time
0x9003 : "DateTimeOriginal", // Date and time when the original image was generated
0x9004 : "DateTimeDigitized", // Date and time when the image was stored digitally
0x9290 : "SubsecTime", // Fractions of seconds for DateTime
0x9291 : "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
0x9292 : "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized
// picture-taking conditions
0x829A : "ExposureTime", // Exposure time (in seconds)
0x829D : "FNumber", // F number
0x8822 : "ExposureProgram", // Exposure program
0x8824 : "SpectralSensitivity", // Spectral sensitivity
0x8827 : "ISOSpeedRatings", // ISO speed rating
0x8828 : "OECF", // Optoelectric conversion factor
0x9201 : "ShutterSpeedValue", // Shutter speed
0x9202 : "ApertureValue", // Lens aperture
0x9203 : "BrightnessValue", // Value of brightness
0x9204 : "ExposureBias", // Exposure bias
0x9205 : "MaxApertureValue", // Smallest F number of lens
0x9206 : "SubjectDistance", // Distance to subject in meters
0x9207 : "MeteringMode", // Metering mode
0x9208 : "LightSource", // Kind of light source
0x9209 : "Flash", // Flash status
0x9214 : "SubjectArea", // Location and area of main subject
0x920A : "FocalLength", // Focal length of the lens in mm
0xA20B : "FlashEnergy", // Strobe energy in BCPS
0xA20C : "SpatialFrequencyResponse", //
0xA20E : "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
0xA20F : "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
0xA210 : "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
0xA214 : "SubjectLocation", // Location of subject in image
0xA215 : "ExposureIndex", // Exposure index selected on camera
0xA217 : "SensingMethod", // Image sensor type
0xA300 : "FileSource", // Image source (3 == DSC)
0xA301 : "SceneType", // Scene type (1 == directly photographed)
0xA302 : "CFAPattern", // Color filter array geometric pattern
0xA401 : "CustomRendered", // Special processing
0xA402 : "ExposureMode", // Exposure mode
0xA403 : "WhiteBalance", // 1 = auto white balance, 2 = manual
0xA404 : "DigitalZoomRation", // Digital zoom ratio
0xA405 : "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
0xA406 : "SceneCaptureType", // Type of scene
0xA407 : "GainControl", // Degree of overall image gain adjustment
0xA408 : "Contrast", // Direction of contrast processing applied by camera
0xA409 : "Saturation", // Direction of saturation processing applied by camera
0xA40A : "Sharpness", // Direction of sharpness processing applied by camera
0xA40B : "DeviceSettingDescription", //
0xA40C : "SubjectDistanceRange", // Distance to subject
// other tags
0xA005 : "InteroperabilityIFDPointer",
0xA420 : "ImageUniqueID" // Identifier assigned uniquely to each image
};
var TiffTags = EXIF.TiffTags = {
0x0100 : "ImageWidth",
0x0101 : "ImageHeight",
0x8769 : "ExifIFDPointer",
0x8825 : "GPSInfoIFDPointer",
0xA005 : "InteroperabilityIFDPointer",
0x0102 : "BitsPerSample",
0x0103 : "Compression",
0x0106 : "PhotometricInterpretation",
0x0112 : "Orientation",
0x0115 : "SamplesPerPixel",
0x011C : "PlanarConfiguration",
0x0212 : "YCbCrSubSampling",
0x0213 : "YCbCrPositioning",
0x011A : "XResolution",
0x011B : "YResolution",
0x0128 : "ResolutionUnit",
0x0111 : "StripOffsets",
0x0116 : "RowsPerStrip",
0x0117 : "StripByteCounts",
0x0201 : "JPEGInterchangeFormat",
0x0202 : "JPEGInterchangeFormatLength",
0x012D : "TransferFunction",
0x013E : "WhitePoint",
0x013F : "PrimaryChromaticities",
0x0211 : "YCbCrCoefficients",
0x0214 : "ReferenceBlackWhite",
0x0132 : "DateTime",
0x010E : "ImageDescription",
0x010F : "Make",
0x0110 : "Model",
0x0131 : "Software",
0x013B : "Artist",
0x8298 : "Copyright"
};
var GPSTags = EXIF.GPSTags = {
0x0000 : "GPSVersionID",
0x0001 : "GPSLatitudeRef",
0x0002 : "GPSLatitude",
0x0003 : "GPSLongitudeRef",
0x0004 : "GPSLongitude",
0x0005 : "GPSAltitudeRef",
0x0006 : "GPSAltitude",
0x0007 : "GPSTimeStamp",
0x0008 : "GPSSatellites",
0x0009 : "GPSStatus",
0x000A : "GPSMeasureMode",
0x000B : "GPSDOP",
0x000C : "GPSSpeedRef",
0x000D : "GPSSpeed",
0x000E : "GPSTrackRef",
0x000F : "GPSTrack",
0x0010 : "GPSImgDirectionRef",
0x0011 : "GPSImgDirection",
0x0012 : "GPSMapDatum",
0x0013 : "GPSDestLatitudeRef",
0x0014 : "GPSDestLatitude",
0x0015 : "GPSDestLongitudeRef",
0x0016 : "GPSDestLongitude",
0x0017 : "GPSDestBearingRef",
0x0018 : "GPSDestBearing",
0x0019 : "GPSDestDistanceRef",
0x001A : "GPSDestDistance",
0x001B : "GPSProcessingMethod",
0x001C : "GPSAreaInformation",
0x001D : "GPSDateStamp",
0x001E : "GPSDifferential"
};
var StringValues = EXIF.StringValues = {
ExposureProgram : {
0 : "Not defined",
1 : "Manual",
2 : "Normal program",
3 : "Aperture priority",
4 : "Shutter priority",
5 : "Creative program",
6 : "Action program",
7 : "Portrait mode",
8 : "Landscape mode"
},
MeteringMode : {
0 : "Unknown",
1 : "Average",
2 : "CenterWeightedAverage",
3 : "Spot",
4 : "MultiSpot",
5 : "Pattern",
6 : "Partial",
255 : "Other"
},
LightSource : {
0 : "Unknown",
1 : "Daylight",
2 : "Fluorescent",
3 : "Tungsten (incandescent light)",
4 : "Flash",
9 : "Fine weather",
10 : "Cloudy weather",
11 : "Shade",
12 : "Daylight fluorescent (D 5700 - 7100K)",
13 : "Day white fluorescent (N 4600 - 5400K)",
14 : "Cool white fluorescent (W 3900 - 4500K)",
15 : "White fluorescent (WW 3200 - 3700K)",
17 : "Standard light A",
18 : "Standard light B",
19 : "Standard light C",
20 : "D55",
21 : "D65",
22 : "D75",
23 : "D50",
24 : "ISO studio tungsten",
255 : "Other"
},
Flash : {
0x0000 : "Flash did not fire",
0x0001 : "Flash fired",
0x0005 : "Strobe return light not detected",
0x0007 : "Strobe return light detected",
0x0009 : "Flash fired, compulsory flash mode",
0x000D : "Flash fired, compulsory flash mode, return light not detected",
0x000F : "Flash fired, compulsory flash mode, return light detected",
0x0010 : "Flash did not fire, compulsory flash mode",
0x0018 : "Flash did not fire, auto mode",
0x0019 : "Flash fired, auto mode",
0x001D : "Flash fired, auto mode, return light not detected",
0x001F : "Flash fired, auto mode, return light detected",
0x0020 : "No flash function",
0x0041 : "Flash fired, red-eye reduction mode",
0x0045 : "Flash fired, red-eye reduction mode, return light not detected",
0x0047 : "Flash fired, red-eye reduction mode, return light detected",
0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode",
0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
0x0059 : "Flash fired, auto mode, red-eye reduction mode",
0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode",
0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode"
},
SensingMethod : {
1 : "Not defined",
2 : "One-chip color area sensor",
3 : "Two-chip color area sensor",
4 : "Three-chip color area sensor",
5 : "Color sequential area sensor",
7 : "Trilinear sensor",
8 : "Color sequential linear sensor"
},
SceneCaptureType : {
0 : "Standard",
1 : "Landscape",
2 : "Portrait",
3 : "Night scene"
},
SceneType : {
1 : "Directly photographed"
},
CustomRendered : {
0 : "Normal process",
1 : "Custom process"
},
WhiteBalance : {
0 : "Auto white balance",
1 : "Manual white balance"
},
GainControl : {
0 : "None",
1 : "Low gain up",
2 : "High gain up",
3 : "Low gain down",
4 : "High gain down"
},
Contrast : {
0 : "Normal",
1 : "Soft",
2 : "Hard"
},
Saturation : {
0 : "Normal",
1 : "Low saturation",
2 : "High saturation"
},
Sharpness : {
0 : "Normal",
1 : "Soft",
2 : "Hard"
},
SubjectDistanceRange : {
0 : "Unknown",
1 : "Macro",
2 : "Close view",
3 : "Distant view"
},
FileSource : {
3 : "DSC"
},
Components : {
0 : "",
1 : "Y",
2 : "Cb",
3 : "Cr",
4 : "R",
5 : "G",
6 : "B"
}
};
function addEvent(element, event, handler) {
if (element.addEventListener) {
element.addEventListener(event, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + event, handler);
}
}
function imageHasData(img) {
return !!(img.exifdata);
}
function base64ToArrayBuffer(base64, contentType) {
contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg'
base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
var binary = atob(base64);
var len = binary.length;
var buffer = new ArrayBuffer(len);
var view = new Uint8Array(buffer);
for (var i = 0; i < len; i++) {
view[i] = binary.charCodeAt(i);
}
return buffer;
}
function objectURLToBlob(url, callback) {
var http = new XMLHttpRequest();
http.open("GET", url, true);
http.responseType = "blob";
http.onload = function(e) {
if (this.status == 200 || this.status === 0) {
callback(this.response);
}
};
http.send();
}
function getImageData(img, callback) {
function handleBinaryFile(binFile) {
var data = findEXIFinJPEG(binFile);
var iptcdata = findIPTCinJPEG(binFile);
img.exifdata = data || {};
img.iptcdata = iptcdata || {};
if (callback) {
callback.call(img);
}
}
if (img.src) {
if (/^data\:/i.test(img.src)) { // Data URI
var arrayBuffer = base64ToArrayBuffer(img.src);
handleBinaryFile(arrayBuffer);
} else if (/^blob\:/i.test(img.src)) { // Object URL
var fileReader = new FileReader();
fileReader.onload = function(e) {
handleBinaryFile(e.target.result);
};
objectURLToBlob(img.src, function (blob) {
fileReader.readAsArrayBuffer(blob);
});
} else {
var http = new XMLHttpRequest();
http.onload = function() {
if (this.status == 200 || this.status === 0) {
handleBinaryFile(http.response);
} else {
throw "Could not load image";
}
http = null;
};
http.open("GET", img.src, true);
http.responseType = "arraybuffer";
http.send(null);
}
} else if (window.FileReader && (img instanceof window.Blob || img instanceof window.File)) {
var fileReader = new FileReader();
fileReader.onload = function(e) {
if (debug) console.log("Got file of length " + e.target.result.byteLength);
handleBinaryFile(e.target.result);
};
fileReader.readAsArrayBuffer(img);
}
}
function findEXIFinJPEG(file) {
var dataView = new DataView(file);
if (debug) console.log("Got file of length " + file.byteLength);
if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
if (debug) console.log("Not a valid JPEG");
return false; // not a valid jpeg
}
var offset = 2,
length = file.byteLength,
marker;
while (offset < length) {
if (dataView.getUint8(offset) != 0xFF) {
if (debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8(offset));
return false; // not a valid marker, something is wrong
}
marker = dataView.getUint8(offset + 1);
if (debug) console.log(marker);
// we could implement handling for other markers here,
// but we're only looking for 0xFFE1 for EXIF data
if (marker == 225) {
if (debug) console.log("Found 0xFFE1 marker");
return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);
// offset += 2 + file.getShortAt(offset+2, true);
} else {
offset += 2 + dataView.getUint16(offset+2);
}
}
}
function findIPTCinJPEG(file) {
var dataView = new DataView(file);
if (debug) console.log("Got file of length " + file.byteLength);
if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
if (debug) console.log("Not a valid JPEG");
return false; // not a valid jpeg
}
var offset = 2,
length = file.byteLength;
var isFieldSegmentStart = function(dataView, offset){
return (
dataView.getUint8(offset) === 0x38 &&
dataView.getUint8(offset+1) === 0x42 &&
dataView.getUint8(offset+2) === 0x49 &&
dataView.getUint8(offset+3) === 0x4D &&
dataView.getUint8(offset+4) === 0x04 &&
dataView.getUint8(offset+5) === 0x04
);
};
while (offset < length) {
if ( isFieldSegmentStart(dataView, offset )){
// Get the length of the name header (which is padded to an even number of bytes)
var nameHeaderLength = dataView.getUint8(offset+7);
if(nameHeaderLength % 2 !== 0) nameHeaderLength += 1;
// Check for pre photoshop 6 format
if(nameHeaderLength === 0) {
// Always 4
nameHeaderLength = 4;
}
var startOffset = offset + 8 + nameHeaderLength;
var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength);
return readIPTCData(file, startOffset, sectionLength);
break;
}
// Not the marker, continue searching
offset++;
}
}
var IptcFieldMap = {
0x78 : 'caption',
0x6E : 'credit',
0x19 : 'keywords',
0x37 : 'dateCreated',
0x50 : 'byline',
0x55 : 'bylineTitle',
0x7A : 'captionWriter',
0x69 : 'headline',
0x74 : 'copyright',
0x0F : 'category'
};
function readIPTCData(file, startOffset, sectionLength){
var dataView = new DataView(file);
var data = {};
var fieldValue, fieldName, dataSize, segmentType, segmentSize;
var segmentStartPos = startOffset;
while(segmentStartPos < startOffset+sectionLength) {
if(dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos+1) === 0x02){
segmentType = dataView.getUint8(segmentStartPos+2);
if(segmentType in IptcFieldMap) {
dataSize = dataView.getInt16(segmentStartPos+3);
segmentSize = dataSize + 5;
fieldName = IptcFieldMap[segmentType];
fieldValue = getStringFromDB(dataView, segmentStartPos+5, dataSize);
// Check if we already stored a value with this name
if(data.hasOwnProperty(fieldName)) {
// Value already stored with this name, create multivalue field
if(data[fieldName] instanceof Array) {
data[fieldName].push(fieldValue);
}
else {
data[fieldName] = [data[fieldName], fieldValue];
}
}
else {
data[fieldName] = fieldValue;
}
}
}
segmentStartPos++;
}
return data;
}
function readTags(file, tiffStart, dirStart, strings, bigEnd) {
var entries = file.getUint16(dirStart, !bigEnd),
tags = {},
entryOffset, tag,
i;
for (i=0;i<entries;i++) {
entryOffset = dirStart + i*12 + 2;
tag = strings[file.getUint16(entryOffset, !bigEnd)];
if (!tag && debug) console.log("Unknown tag: " + file.getUint16(entryOffset, !bigEnd));
tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);
}
return tags;
}
function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) {
var type = file.getUint16(entryOffset+2, !bigEnd),
numValues = file.getUint32(entryOffset+4, !bigEnd),
valueOffset = file.getUint32(entryOffset+8, !bigEnd) + tiffStart,
offset,
vals, val, n,
numerator, denominator;
switch (type) {
case 1: // byte, 8-bit unsigned int
case 7: // undefined, 8-bit byte, value depending on field
if (numValues == 1) {
return file.getUint8(entryOffset + 8, !bigEnd);
} else {
offset = numValues > 4 ? valueOffset : (entryOffset + 8);
vals = [];
for (n=0;n<numValues;n++) {
vals[n] = file.getUint8(offset + n);
}
return vals;
}
case 2: // ascii, 8-bit byte
offset = numValues > 4 ? valueOffset : (entryOffset + 8);
return getStringFromDB(file, offset, numValues-1);
case 3: // short, 16 bit int
if (numValues == 1) {
return file.getUint16(entryOffset + 8, !bigEnd);
} else {
offset = numValues > 2 ? valueOffset : (entryOffset + 8);
vals = [];
for (n=0;n<numValues;n++) {
vals[n] = file.getUint16(offset + 2*n, !bigEnd);
}
return vals;
}
case 4: // long, 32 bit int
if (numValues == 1) {
return file.getUint32(entryOffset + 8, !bigEnd);
} else {
vals = [];
for (n=0;n<numValues;n++) {
vals[n] = file.getUint32(valueOffset + 4*n, !bigEnd);
}
return vals;
}
case 5: // rational = two long values, first is numerator, second is denominator
if (numValues == 1) {
numerator = file.getUint32(valueOffset, !bigEnd);
denominator = file.getUint32(valueOffset+4, !bigEnd);
val = new Number(numerator / denominator);
val.numerator = numerator;
val.denominator = denominator;
return val;
} else {
vals = [];
for (n=0;n<numValues;n++) {
numerator = file.getUint32(valueOffset + 8*n, !bigEnd);
denominator = file.getUint32(valueOffset+4 + 8*n, !bigEnd);
vals[n] = new Number(numerator / denominator);
vals[n].numerator = numerator;
vals[n].denominator = denominator;
}
return vals;
}
case 9: // slong, 32 bit signed int
if (numValues == 1) {
return file.getInt32(entryOffset + 8, !bigEnd);
} else {
vals = [];
for (n=0;n<numValues;n++) {
vals[n] = file.getInt32(valueOffset + 4*n, !bigEnd);
}
return vals;
}
case 10: // signed rational, two slongs, first is numerator, second is denominator
if (numValues == 1) {
return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset+4, !bigEnd);
} else {
vals = [];
for (n=0;n<numValues;n++) {
vals[n] = file.getInt32(valueOffset + 8*n, !bigEnd) / file.getInt32(valueOffset+4 + 8*n, !bigEnd);
}
return vals;
}
}
}
function getStringFromDB(buffer, start, length) {
var outstr = "";
for (var n = start; n < start+length; n++) {
outstr += String.fromCharCode(buffer.getUint8(n));
}
return outstr;
}
function readEXIFData(file, start) {
if (getStringFromDB(file, start, 4) != "Exif") {
if (debug) console.log("Not valid EXIF data! " + getStringFromDB(file, start, 4));
return false;
}
var bigEnd,
tags, tag,
exifData, gpsData,
tiffOffset = start + 6;
// test for TIFF validity and endianness
if (file.getUint16(tiffOffset) == 0x4949) {
bigEnd = false;
} else if (file.getUint16(tiffOffset) == 0x4D4D) {
bigEnd = true;
} else {
if (debug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
return false;
}
if (file.getUint16(tiffOffset+2, !bigEnd) != 0x002A) {
if (debug) console.log("Not valid TIFF data! (no 0x002A)");
return false;
}
var firstIFDOffset = file.getUint32(tiffOffset+4, !bigEnd);
if (firstIFDOffset < 0x00000008) {
if (debug) console.log("Not valid TIFF data! (First offset less than 8)", file.getUint32(tiffOffset+4, !bigEnd));
return false;
}
tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd);
if (tags.ExifIFDPointer) {
exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd);
for (tag in exifData) {
switch (tag) {
case "LightSource" :
case "Flash" :
case "MeteringMode" :
case "ExposureProgram" :
case "SensingMethod" :
case "SceneCaptureType" :
case "SceneType" :
case "CustomRendered" :
case "WhiteBalance" :
case "GainControl" :
case "Contrast" :
case "Saturation" :
case "Sharpness" :
case "SubjectDistanceRange" :
case "FileSource" :
exifData[tag] = StringValues[tag][exifData[tag]];
break;
case "ExifVersion" :
case "FlashpixVersion" :
exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]);
break;
case "ComponentsConfiguration" :
exifData[tag] =
StringValues.Components[exifData[tag][0]] +
StringValues.Components[exifData[tag][1]] +
StringValues.Components[exifData[tag][2]] +
StringValues.Components[exifData[tag][3]];
break;
}
tags[tag] = exifData[tag];
}
}
if (tags.GPSInfoIFDPointer) {
gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd);
for (tag in gpsData) {
switch (tag) {
case "GPSVersionID" :
gpsData[tag] = gpsData[tag][0] +
"." + gpsData[tag][1] +
"." + gpsData[tag][2] +
"." + gpsData[tag][3];
break;
}
tags[tag] = gpsData[tag];
}
}
return tags;
}
EXIF.getData = function(img, callback) {
if ((img instanceof Image || img instanceof HTMLImageElement) && !img.complete) return false;
if (!imageHasData(img)) {
getImageData(img, callback);
} else {
if (callback) {
callback.call(img);
}
}
return true;
}
EXIF.getTag = function(img, tag) {
if (!imageHasData(img)) return;
return img.exifdata[tag];
}
EXIF.getAllTags = function(img) {
if (!imageHasData(img)) return {};
var a,
data = img.exifdata,
tags = {};
for (a in data) {
if (data.hasOwnProperty(a)) {
tags[a] = data[a];
}
}
return tags;
}
EXIF.pretty = function(img) {
if (!imageHasData(img)) return "";
var a,
data = img.exifdata,
strPretty = "";
for (a in data) {
if (data.hasOwnProperty(a)) {
if (typeof data[a] == "object") {
if (data[a] instanceof Number) {
strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a].denominator + "]\r\n";
} else {
strPretty += a + " : [" + data[a].length + " values]\r\n";
}
} else {
strPretty += a + " : " + data[a] + "\r\n";
}
}
}
return strPretty;
}
EXIF.readFromBinaryFile = function(file) {
return findEXIFinJPEG(file);
}
export default EXIF
/* eslint-disable */
// 获取图片的 mimetype
// algorithm come from https://mimesniff.spec.whatwg.org/#matching-an-image-type-pattern
var IMAGES_PATTERNS = [
{
"bytePattern": [0x00, 0x00, 0x01, 0x00],
"patternMask": [0xff, 0xff, 0xff, 0xff],
"imageType": "image/x-icon"
},
{
"bytePattern": [0x00, 0x00, 0x02, 0x00],
"patternMask": [0xff, 0xff, 0xff, 0xff],
"imageType": "image/x-icon"
},
{
"bytePattern": [0x42, 0x4d],
"patternMask": [0xff, 0xff],
"imageType": "image/bmp"
},
{
"bytePattern": [0x47, 0x49, 0x46, 0x38, 0x37, 0x61],
"patternMask": [0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
"imageType": "image/gif"
},
{
"bytePattern": [0x47, 0x49, 0x46, 0x38, 0x39, 0x61],
"patternMask": [0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
"imageType": "image/gif"
},
{
"bytePattern": [0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50],
"patternMask": [0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
"imageType": "image/webp"
},
{
"bytePattern": [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
"patternMask": [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
"imageType": "image/png"
},
{
"bytePattern": [0xff, 0xd8, 0xff],
"patternMask": [0xff, 0xff, 0xff],
"imageType": "image/jpeg"
}
];
function getImgMimeType(sequence) {
// sequence 为图片字节数组
var sequenceLen = sequence.length;
// 返回图片类型
var retType = null;
// 遍历图片模式,检测文件类型
for (var i = IMAGES_PATTERNS.length - 1; i >= 0; i--) {
// 当前文件模式
var cur = IMAGES_PATTERNS[i];
// 当前文件头模式
var bp = cur.bytePattern;
// 当前文件头模式掩码,用于确定文件签名字节
var pm = cur.patternMask;
var length = bp.length;
// 如果文件的字节数还没有模式的长度长,说明不是一个有效的该模式文件,继续检查下一个模式
if (sequenceLen < length) {
continue;
}
// 循环检查文件头的内容,确定文件类型
for (var j = 0; j < length; j++) {
// 只有当文件头字节序列中对应位置的掩码为 0xff 时,才确定是文件签名字节
if (pm[j] !== 0xff) {
continue;
}
// 判断当前文件头字节序列是否与指定模式相匹配
if (bp[j] !== sequence[j]) {
break;
}
}
if (j >= length) {
return (retType = cur.imageType);
}
}
return retType;
}
export default getImgMimeType;
/* eslint-disable */
// 基于 canvas 压缩图片为 jpg 格式
// 不支持裁剪,只做压缩
/*=============================================================================================*/
// 已发现并修复的 andriod bug:
// 1.调用 canvas.toDataURL('image/jpeg') 返回 image/png,导致图片变大
// 解决:
// 使用 JPEGEncode 生成 jpg
// 2.调用 FileReader.readDataAsURL() 没有返回 mime type,导致图片载入失败
// 解决:
// 侦测文件类型,将获取的 mime type 补进 base64
/*=============================================================================================*/
/*=============================================================================================*/
// 已发现并修复的 ios bug:
// ios bug 修复参考:https://github.com/stomita/ios-imagefile-megapixel
/*=============================================================================================*/
import JPEGEncoder from './jpegencoder';
import EXIF from './exif';
import blobutil from './blobutil';
import imagemime from './imagemime';
var ua = window.navigator.userAgent;
var android = /Android/.test(ua);
var ios = /(?:iPhone|iPod|iPad)/.test(ua);
/**
* Detect subsampling in loaded image.
* In iOS, larger images than 2M pixels may be subsampled in rendering.
*
* 用载入的图片检测二次抽样
* 在 ios 中,如果图片的像素数大于 2M 的话,渲染的时候可能会被二次抽样
*/
function detectSubsampling(img) {
var iw = img.naturalWidth || img.width;
var ih = img.naturalHeight || img.height;
if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, -iw + 1, 0);
// subsampled image becomes half smaller in rendering size.
// check alpha channel value to confirm image is covering edge pixel or not.
// if alpha value is 0 image is not covering, hence subsampled.
// 渲染的时候,二次抽样后的图片会变成一半的大小。
// 检查 alpha 通道的值来确定图片是否覆盖了边缘像素。
// 如果 alpha 的值是 0 的话,说明图片没有覆盖边缘像素,于是可以确定图片已经被二次抽样了。
return ctx.getImageData(0, 0, 1, 1).data[3] === 0;
} else {
return false;
}
}
/**
* Detecting vertical squash in loaded image.
* Fixes a bug which squash image vertically while drawing into canvas for some images.
*
* 用载入的图片检测垂直压扁
* 修复了一个这样的BUG:当绘制一些图片到画布中时,图片会被垂直压扁
*/
function detectVerticalSquash(img, iw, ih) {
var canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = ih;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
var data = ctx.getImageData(0, 0, 1, ih).data;
// search image edge pixel position in case it is squashed vertically.
var sy = 0;
var ey = ih;
var py = ih;
while (py > sy) {
var alpha = data[(py - 1) * 4 + 3];
if (alpha === 0) {
ey = py;
} else {
sy = py;
}
py = (ey + sy) >> 1;
}
var ratio = (py / ih);
return (ratio === 0) ? 1 : ratio;
}
/**
* Transform canvas coordination according to specified frame size and orientation
* Orientation value is from EXIF tag
*/
function transformCoordinate(canvas, ctx, width, height, orientation) {
switch (orientation) {
case 5:
case 6:
case 7:
case 8:
canvas.width = height;
canvas.height = width;
break;
default:
canvas.width = width;
canvas.height = height;
}
switch (orientation) {
case 2:
// horizontal flip
ctx.translate(width, 0);
ctx.scale(-1, 1);
break;
case 3:
// 180 rotate left
ctx.translate(width, height);
ctx.rotate(Math.PI);
break;
case 4:
// vertical flip
ctx.translate(0, height);
ctx.scale(1, -1);
break;
case 5:
// vertical flip + 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.scale(1, -1);
break;
case 6:
// 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.translate(0, -height);
break;
case 7:
// horizontal flip + 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.translate(width, -height);
ctx.scale(-1, 1);
break;
case 8:
// 90 rotate left
ctx.rotate(-0.5 * Math.PI);
ctx.translate(-width, 0);
break;
default:
break;
}
}
// 压缩图片
// file 为从 input file 控件中选择的文件对象
// options:
// {
// quality: 80 压缩质量,数值越大质量越高,反之则越小
// size: 640 压缩后的图片大小,按长边等比例缩放至 size 指定的大小
// cb: function(dataURL) {
// // 压缩后的回调函数
// // dataURL 为jpg数据的base64字符串
// }
// }
function androidCompress(file, options) {
var reader = new FileReader();
reader.onloadend = function(e) {
// reader 对象
var host = e.target;
// 读取的文件内容
var data = host.result;
// 错误对象
var error= host.error;
// 错误消息
var errorMsg;
// 二进制字节数组
var rawBinary;
// 内存图片
var memImg;
// 图片 mime type
var mimeType;
if (error != null) {
switch (error.code) {
case error.NOT_FOUND_ERR:
errorMsg = 'File not found!';
break;
case error.SECURITY_ERR:
errorMsg = 'Security issue with file!';
break;
case error.NOT_READABLE_ERR:
errorMsg = 'File could not be read!';
break;
case error.ENCODING_ERR:
errorMsg = 'Encoding error!';
break;
default:
errorMsg = 'Unknown issue!';
};
throw new Error(errorMsg);
} else {
// 检测文件类型必须为图片
rawBinary = blobutil.base64ToArrayBuffer(data);
mimeType = imagemime(rawBinary);
if (mimeType == null) {
throw new Error('请选择图片');
}
// 如果没有返回 mime type ,则自动补
if (!/^data:image\/.+?;base64,/i.test(data)) {
data = 'data:' + mimeType + ';base64,' + data.split(',')[1];
}
// 载入内存图片,根据配置压缩图片
memImg = new Image();
memImg.onload = function() {
// 获取选择的图片的方向信息
var tags = EXIF.readFromBinaryFile(rawBinary.buffer);
var orientation = tags.Orientation || 1;
// 配置参数
var opts = Object.create(options || {});
var optsQuality = opts.quality || 80;
var optsSize = opts.size || 640;
var optsCb = opts.cb;
// 原始图尺寸
var iw = this.naturalWidth || this.width;
var ih = this.naturalHeight || this.height;
// 原始图长边大小
var lss = iw > ih ? iw : ih;
// 压缩图尺寸
var cw;
var ch;
// 画布
var canvas;
var ctx;
var dataURL;
if (optsSize < lss) {
// 指定的大小比原图小,计算长边的缩放比例,从而计算出短边的大小
if (iw > ih) {
cw = optsSize;
// 向左移动 0 位的作用是转换为整数
ch = (ih * cw / iw) << 0;
} else {
ch = optsSize;
// 向左移动 0 位的作用是转换为整数
cw = (iw * ch / ih) << 0;
}
} else {
// 指定的大小比原图大,则使用原图尺寸压缩
cw = iw;
ch = ih;
}
// 根据图片方向信息设置画布宽高,变换画布坐标系
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
transformCoordinate(canvas, ctx, cw, ch, orientation);
ctx.drawImage(this, 0, 0, cw, ch);
// 导出压缩后的图片数据
dataURL = canvas.toDataURL('image/jpeg', optsQuality / 100);
// 部分设备可能无法正确导出 jpeg data url
if (!/^data:image\/jpeg;base64,.+$/i.test(dataURL)) {
// 压缩图片为 jpg 格式
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
dataURL = new JPEGEncoder().encode(imageData, optsQuality);
}
if (optsCb) {
optsCb(dataURL);
}
};
memImg.onerror = function() {
throw new Error('不支持' + mimeType + '格式');
};
memImg.src = data;
}
};
reader.readAsDataURL(file);
}
function iosCompress(file, options) {
var reader = new FileReader();
reader.onloadend = function(e) {
// reader 对象
var host = e.target;
// 读取的文件内容
var data = host.result;
// 错误对象
var error= host.error;
// 错误消息
var errorMsg;
// 二进制字节数组
var rawBinary;
// 内存图片
var memImg;
// 图片 mime type
var mimeType;
if (error != null) {
switch (error.code) {
case error.NOT_FOUND_ERR:
errorMsg = 'File not found!';
break;
case error.SECURITY_ERR:
errorMsg = 'Security issue with file!';
break;
case error.NOT_READABLE_ERR:
errorMsg = 'File could not be read!';
break;
case error.ENCODING_ERR:
errorMsg = 'Encoding error!';
break;
default:
errorMsg = 'Unknown issue!';
};
throw new Error(errorMsg);
} else {
// 检测文件类型必须为图片
mimeType = file.type;
if (!/^image\/.+/i.test(mimeType)) {
throw new Error('请选择图片');
}
// 载入内存图片,根据配置压缩图片
memImg = new Image();
memImg.onload = function() {
// 获取选择的图片的方向信息
rawBinary = blobutil.base64ToArrayBuffer(data);
var tags = EXIF.readFromBinaryFile(rawBinary.buffer);
var orientation = tags.Orientation || 1;
// 配置参数
var opts = Object.create(options || {});
var optsQuality = opts.quality || 80;
var optsSize = opts.size || 640;
var optsCb = opts.cb;
// 原始图尺寸
var iw = this.naturalWidth || this.width;
var ih = this.naturalHeight || this.height;
// 原始图长边大小
var lss = iw > ih ? iw : ih;
// 压缩图尺寸
var cw;
var ch;
var cr;
// 画布
var canvas;
var ctx;
var subsampled;
var d;
var tmpCanvas;
var tmpCtx;
var vertSquashRatio;
var dx;
var dy;
var dw;
var dh;
var sx;
var sy;
var sw;
var sh;
var dataURL;
var imageData;
var jpegURL;
var doSquash = mimeType === 'image/jpeg';
if (optsSize < lss) {
// 指定的大小比原图小,计算长边的缩放比例,从而计算出短边的大小
if (iw > ih) {
cw = optsSize;
// 向左移动 0 位的作用是转换为整数
ch = (ih * cw / iw) << 0;
} else {
ch = optsSize;
// 向左移动 0 位的作用是转换为整数
cw = (iw * ch / ih) << 0;
}
} else {
// 指定的大小比原图大,则使用原图尺寸压缩
cw = iw;
ch = ih;
}
// 根据图片方向信息设置画布宽高,变换画布坐标系
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
ctx.save();
transformCoordinate(canvas, ctx, cw, ch, orientation);
// 图片二次抽样处理
subsampled = detectSubsampling(this);
if (subsampled) {
iw /= 2;
ih /= 2;
}
// 超大图片分块处理
// 将一幅大图分成若干个小块绘制到目标画布上
// 每个块的大小为 1024 * 1024
d = 1024;
tmpCanvas = document.createElement('canvas');
tmpCanvas.width = tmpCanvas.height = d;
tmpCtx = tmpCanvas.getContext('2d');
vertSquashRatio = doSquash ? detectVerticalSquash(this, iw, ih) : 1;
sy = 0;
// 分割块循环
while (sy < ih) {
// 计算分割块高
sh = sy + d > ih ? ih - sy : d;
sx = 0;
while (sx < iw) {
// 计算分割块宽
sw = sx + d > iw ? iw - sx : d;
// 清空临时画布上的所有内容
tmpCtx.clearRect(0, 0, d, d);
// 将整个源图绘制到临时画布上的指定位置,通过指定负坐标将不是当前块的内容绘制到临时画布以外,从而
// 画布以内的区域就是当前块
tmpCtx.drawImage(this, -sx, -sy);
// 当前块绘制到目标画布上的 x 轴的指定位置(如果有缩放进行缩放)
dx = Math.floor(sx * cw / iw);
// 当前块绘制到目标画布上的宽(如果有缩放进行缩放)
dw = Math.ceil(sw * cw / iw);
// 当前块绘制到目标画布上的 y 轴的指定位置(如果有缩放进行缩放,同时处理垂直压扁比例)
dy = Math.floor(sy * ch / ih / vertSquashRatio);
// 当前块绘制到目标画布上的高(如果有缩放进行缩放,同时处理垂直压扁比例)
dh = Math.ceil(sh * ch / ih / vertSquashRatio);
// 绘制当前块到目标画布上的指定位置
ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh);
sx += d;
}
sy += d;
}
ctx.restore();
tmpCanvas = tmpCtx = null;
// 导出压缩后的图片数据
dataURL = canvas.toDataURL('image/jpeg', optsQuality / 100);
if (optsCb) {
optsCb(dataURL);
}
};
memImg.onerror = function() {
throw new Error('不支持' + mimeType + '格式');
};
memImg.src = data;
}
};
reader.readAsDataURL(file);
}
var compress = (function() {
if (ios) {
return iosCompress;
}
return androidCompress;
})();
export default compress
/* eslint-disable */
/*
Basic GUI blocking jpeg encoder ported to JavaScript and optimized by
Andreas Ritter, www.bytestrom.eu, 11/2009.
Example usage is given at the bottom of this file.
---------
Copyright (c) 2008, Adobe Systems Incorporated
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Adobe Systems Incorporated nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
function JPEGEncoder(quality) {
var self = this;
var fround = Math.round;
var ffloor = Math.floor;
var YTable = new Array(64);
var UVTable = new Array(64);
var fdtbl_Y = new Array(64);
var fdtbl_UV = new Array(64);
var YDC_HT;
var UVDC_HT;
var YAC_HT;
var UVAC_HT;
var bitcode = new Array(65535);
var category = new Array(65535);
var outputfDCTQuant = new Array(64);
var DU = new Array(64);
var byteout = [];
var bytenew = 0;
var bytepos = 7;
var YDU = new Array(64);
var UDU = new Array(64);
var VDU = new Array(64);
var clt = new Array(256);
var RGB_YUV_TABLE = new Array(2048);
var currentQuality;
var ZigZag = [
0, 1, 5, 6,14,15,27,28,
2, 4, 7,13,16,26,29,42,
3, 8,12,17,25,30,41,43,
9,11,18,24,31,40,44,53,
10,19,23,32,39,45,52,54,
20,22,33,38,46,51,55,60,
21,34,37,47,50,56,59,61,
35,36,48,49,57,58,62,63
];
var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
var std_ac_luminance_values = [
0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
];
var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
var std_ac_chrominance_values = [
0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
];
function initQuantTables(sf){
var YQT = [
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68,109,103, 77,
24, 35, 55, 64, 81,104,113, 92,
49, 64, 78, 87,103,121,120,101,
72, 92, 95, 98,112,100,103, 99
];
for (var i = 0; i < 64; i++) {
var t = ffloor((YQT[i]*sf+50)/100);
if (t < 1) {
t = 1;
} else if (t > 255) {
t = 255;
}
YTable[ZigZag[i]] = t;
}
var UVQT = [
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
];
for (var j = 0; j < 64; j++) {
var u = ffloor((UVQT[j]*sf+50)/100);
if (u < 1) {
u = 1;
} else if (u > 255) {
u = 255;
}
UVTable[ZigZag[j]] = u;
}
var aasf = [
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379
];
var k = 0;
for (var row = 0; row < 8; row++)
{
for (var col = 0; col < 8; col++)
{
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
k++;
}
}
}
function computeHuffmanTbl(nrcodes, std_table){
var codevalue = 0;
var pos_in_table = 0;
var HT = new Array();
for (var k = 1; k <= 16; k++) {
for (var j = 1; j <= nrcodes[k]; j++) {
HT[std_table[pos_in_table]] = [];
HT[std_table[pos_in_table]][0] = codevalue;
HT[std_table[pos_in_table]][1] = k;
pos_in_table++;
codevalue++;
}
codevalue*=2;
}
return HT;
}
function initHuffmanTbl()
{
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
}
function initCategoryNumber()
{
var nrlower = 1;
var nrupper = 2;
for (var cat = 1; cat <= 15; cat++) {
//Positive numbers
for (var nr = nrlower; nr<nrupper; nr++) {
category[32767+nr] = cat;
bitcode[32767+nr] = [];
bitcode[32767+nr][1] = cat;
bitcode[32767+nr][0] = nr;
}
//Negative numbers
for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {
category[32767+nrneg] = cat;
bitcode[32767+nrneg] = [];
bitcode[32767+nrneg][1] = cat;
bitcode[32767+nrneg][0] = nrupper-1+nrneg;
}
nrlower <<= 1;
nrupper <<= 1;
}
}
function initRGBYUVTable() {
for(var i = 0; i < 256;i++) {
RGB_YUV_TABLE[i] = 19595 * i;
RGB_YUV_TABLE[(i+ 256)>>0] = 38470 * i;
RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000;
RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i;
RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i;
RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF;
RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i;
RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i;
}
}
// IO functions
function writeBits(bs)
{
var value = bs[0];
var posval = bs[1]-1;
while ( posval >= 0 ) {
if (value & (1 << posval) ) {
bytenew |= (1 << bytepos);
}
posval--;
bytepos--;
if (bytepos < 0) {
if (bytenew == 0xFF) {
writeByte(0xFF);
writeByte(0);
}
else {
writeByte(bytenew);
}
bytepos=7;
bytenew=0;
}
}
}
function writeByte(value)
{
byteout.push(clt[value]); // write char directly instead of converting later
}
function writeWord(value)
{
writeByte((value>>8)&0xFF);
writeByte((value )&0xFF);
}
// DCT & quantization core
function fDCTQuant(data, fdtbl)
{
var d0, d1, d2, d3, d4, d5, d6, d7;
/* Pass 1: process rows. */
var dataOff=0;
var i;
const I8 = 8;
const I64 = 64;
for (i=0; i<I8; ++i)
{
d0 = data[dataOff];
d1 = data[dataOff+1];
d2 = data[dataOff+2];
d3 = data[dataOff+3];
d4 = data[dataOff+4];
d5 = data[dataOff+5];
d6 = data[dataOff+6];
d7 = data[dataOff+7];
var tmp0 = d0 + d7;
var tmp7 = d0 - d7;
var tmp1 = d1 + d6;
var tmp6 = d1 - d6;
var tmp2 = d2 + d5;
var tmp5 = d2 - d5;
var tmp3 = d3 + d4;
var tmp4 = d3 - d4;
/* Even part */
var tmp10 = tmp0 + tmp3; /* phase 2 */
var tmp13 = tmp0 - tmp3;
var tmp11 = tmp1 + tmp2;
var tmp12 = tmp1 - tmp2;
data[dataOff] = tmp10 + tmp11; /* phase 3 */
data[dataOff+4] = tmp10 - tmp11;
var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */
data[dataOff+2] = tmp13 + z1; /* phase 5 */
data[dataOff+6] = tmp13 - z1;
/* Odd part */
tmp10 = tmp4 + tmp5; /* phase 2 */
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */
var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */
var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */
var z3 = tmp11 * 0.707106781; /* c4 */
var z11 = tmp7 + z3; /* phase 5 */
var z13 = tmp7 - z3;
data[dataOff+5] = z13 + z2; /* phase 6 */
data[dataOff+3] = z13 - z2;
data[dataOff+1] = z11 + z4;
data[dataOff+7] = z11 - z4;
dataOff += 8; /* advance pointer to next row */
}
/* Pass 2: process columns. */
dataOff = 0;
for (i=0; i<I8; ++i)
{
d0 = data[dataOff];
d1 = data[dataOff + 8];
d2 = data[dataOff + 16];
d3 = data[dataOff + 24];
d4 = data[dataOff + 32];
d5 = data[dataOff + 40];
d6 = data[dataOff + 48];
d7 = data[dataOff + 56];
var tmp0p2 = d0 + d7;
var tmp7p2 = d0 - d7;
var tmp1p2 = d1 + d6;
var tmp6p2 = d1 - d6;
var tmp2p2 = d2 + d5;
var tmp5p2 = d2 - d5;
var tmp3p2 = d3 + d4;
var tmp4p2 = d3 - d4;
/* Even part */
var tmp10p2 = tmp0p2 + tmp3p2; /* phase 2 */
var tmp13p2 = tmp0p2 - tmp3p2;
var tmp11p2 = tmp1p2 + tmp2p2;
var tmp12p2 = tmp1p2 - tmp2p2;
data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */
data[dataOff+32] = tmp10p2 - tmp11p2;
var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
data[dataOff+48] = tmp13p2 - z1p2;
/* Odd part */
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
tmp11p2 = tmp5p2 + tmp6p2;
tmp12p2 = tmp6p2 + tmp7p2;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
var z3p2 = tmp11p2 * 0.707106781; /* c4 */
var z11p2 = tmp7p2 + z3p2; /* phase 5 */
var z13p2 = tmp7p2 - z3p2;
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
data[dataOff+24] = z13p2 - z2p2;
data[dataOff+ 8] = z11p2 + z4p2;
data[dataOff+56] = z11p2 - z4p2;
dataOff++; /* advance pointer to next column */
}
// Quantize/descale the coefficients
var fDCTQuant;
for (i=0; i<I64; ++i)
{
// Apply the quantization and scaling factor & Round to nearest integer
fDCTQuant = data[i]*fdtbl[i];
outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);
//outputfDCTQuant[i] = fround(fDCTQuant);
}
return outputfDCTQuant;
}
function writeAPP0()
{
writeWord(0xFFE0); // marker
writeWord(16); // length
writeByte(0x4A); // J
writeByte(0x46); // F
writeByte(0x49); // I
writeByte(0x46); // F
writeByte(0); // = "JFIF",'\0'
writeByte(1); // versionhi
writeByte(1); // versionlo
writeByte(0); // xyunits
writeWord(1); // xdensity
writeWord(1); // ydensity
writeByte(0); // thumbnwidth
writeByte(0); // thumbnheight
}
function writeSOF0(width, height)
{
writeWord(0xFFC0); // marker
writeWord(17); // length, truecolor YUV JPG
writeByte(8); // precision
writeWord(height);
writeWord(width);
writeByte(3); // nrofcomponents
writeByte(1); // IdY
writeByte(0x11); // HVY
writeByte(0); // QTY
writeByte(2); // IdU
writeByte(0x11); // HVU
writeByte(1); // QTU
writeByte(3); // IdV
writeByte(0x11); // HVV
writeByte(1); // QTV
}
function writeDQT()
{
writeWord(0xFFDB); // marker
writeWord(132); // length
writeByte(0);
for (var i=0; i<64; i++) {
writeByte(YTable[i]);
}
writeByte(1);
for (var j=0; j<64; j++) {
writeByte(UVTable[j]);
}
}
function writeDHT()
{
writeWord(0xFFC4); // marker
writeWord(0x01A2); // length
writeByte(0); // HTYDCinfo
for (var i=0; i<16; i++) {
writeByte(std_dc_luminance_nrcodes[i+1]);
}
for (var j=0; j<=11; j++) {
writeByte(std_dc_luminance_values[j]);
}
writeByte(0x10); // HTYACinfo
for (var k=0; k<16; k++) {
writeByte(std_ac_luminance_nrcodes[k+1]);
}
for (var l=0; l<=161; l++) {
writeByte(std_ac_luminance_values[l]);
}
writeByte(1); // HTUDCinfo
for (var m=0; m<16; m++) {
writeByte(std_dc_chrominance_nrcodes[m+1]);
}
for (var n=0; n<=11; n++) {
writeByte(std_dc_chrominance_values[n]);
}
writeByte(0x11); // HTUACinfo
for (var o=0; o<16; o++) {
writeByte(std_ac_chrominance_nrcodes[o+1]);
}
for (var p=0; p<=161; p++) {
writeByte(std_ac_chrominance_values[p]);
}
}
function writeSOS()
{
writeWord(0xFFDA); // marker
writeWord(12); // length
writeByte(3); // nrofcomponents
writeByte(1); // IdY
writeByte(0); // HTY
writeByte(2); // IdU
writeByte(0x11); // HTU
writeByte(3); // IdV
writeByte(0x11); // HTV
writeByte(0); // Ss
writeByte(0x3f); // Se
writeByte(0); // Bf
}
function processDU(CDU, fdtbl, DC, HTDC, HTAC){
var EOB = HTAC[0x00];
var M16zeroes = HTAC[0xF0];
var pos;
const I16 = 16;
const I63 = 63;
const I64 = 64;
var DU_DCT = fDCTQuant(CDU, fdtbl);
//ZigZag reorder
for (var j=0;j<I64;++j) {
DU[ZigZag[j]]=DU_DCT[j];
}
var Diff = DU[0] - DC; DC = DU[0];
//Encode DC
if (Diff==0) {
writeBits(HTDC[0]); // Diff might be 0
} else {
pos = 32767+Diff;
writeBits(HTDC[category[pos]]);
writeBits(bitcode[pos]);
}
//Encode ACs
var end0pos = 63; // was const... which is crazy
for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};
//end0pos = first element in reverse order !=0
if ( end0pos == 0) {
writeBits(EOB);
return DC;
}
var i = 1;
var lng;
while ( i <= end0pos ) {
var startpos = i;
for (; (DU[i]==0) && (i<=end0pos); ++i) {}
var nrzeroes = i-startpos;
if ( nrzeroes >= I16 ) {
lng = nrzeroes>>4;
for (var nrmarker=1; nrmarker <= lng; ++nrmarker)
writeBits(M16zeroes);
nrzeroes = nrzeroes&0xF;
}
pos = 32767+DU[i];
writeBits(HTAC[(nrzeroes<<4)+category[pos]]);
writeBits(bitcode[pos]);
i++;
}
if ( end0pos != I63 ) {
writeBits(EOB);
}
return DC;
}
function initCharLookupTable(){
var sfcc = String.fromCharCode;
for(var i=0; i < 256; i++){ ///// ACHTUNG // 255
clt[i] = sfcc(i);
}
}
this.encode = function(image,quality,toRaw) // image data object
{
var time_start = new Date().getTime();
if(quality) setQuality(quality);
// Initialize bit writer
byteout = new Array();
bytenew=0;
bytepos=7;
// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0(image.width,image.height);
writeDHT();
writeSOS();
// Encode 8x8 macroblocks
var DCY=0;
var DCU=0;
var DCV=0;
bytenew=0;
bytepos=7;
this.encode.displayName = "_encode_";
var imageData = image.data;
var width = image.width;
var height = image.height;
var quadWidth = width*4;
var tripleWidth = width*3;
var x, y = 0;
var r, g, b;
var start,p, col,row,pos;
while(y < height){
x = 0;
while(x < quadWidth){
start = quadWidth * y + x;
p = start;
col = -1;
row = 0;
for(pos=0; pos < 64; pos++){
row = pos >> 3;// /8
col = ( pos & 7 ) * 4; // %8
p = start + ( row * quadWidth ) + col;
if(y+row >= height){ // padding bottom
p-= (quadWidth*(y+1+row-height));
}
if(x+col >= quadWidth){ // padding right
p-= ((x+col) - quadWidth +4)
}
r = imageData[ p++ ];
g = imageData[ p++ ];
b = imageData[ p++ ];
/* // calculate YUV values dynamically
YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80
UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));
VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));
*/
// use lookup table (slightly faster)
YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128;
UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;
VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;
}
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
x+=32;
}
y+=8;
}
////////////////////////////////////////////////////////////////
// Do the bit alignment of the EOI marker
if ( bytepos >= 0 ) {
var fillbits = [];
fillbits[1] = bytepos+1;
fillbits[0] = (1<<(bytepos+1))-1;
writeBits(fillbits);
}
writeWord(0xFFD9); //EOI
if(toRaw) {
var len = byteout.length;
var data = new Uint8Array(len);
for (var i=0; i<len; i++ ) {
data[i] = byteout[i].charCodeAt();
}
//cleanup
byteout = [];
// benchmarking
var duration = new Date().getTime() - time_start;
console.log('Encoding time: '+ duration + 'ms');
return data;
}
var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join(''));
byteout = [];
// benchmarking
var duration = new Date().getTime() - time_start;
console.log('Encoding time: '+ duration + 'ms');
return jpegDataUri
}
function setQuality(quality){
if (quality <= 0) {
quality = 1;
}
if (quality > 100) {
quality = 100;
}
if(currentQuality == quality) return // don't recalc if unchanged
var sf = 0;
if (quality < 50) {
sf = Math.floor(5000 / quality);
} else {
sf = Math.floor(200 - quality*2);
}
initQuantTables(sf);
currentQuality = quality;
console.log('Quality set to: '+quality +'%');
}
function init(){
var time_start = new Date().getTime();
if(!quality) quality = 50;
// Create tables
initCharLookupTable()
initHuffmanTbl();
initCategoryNumber();
initRGBYUVTable();
setQuality(quality);
var duration = new Date().getTime() - time_start;
console.log('Initialization '+ duration + 'ms');
}
init();
};
export default JPEGEncoder;
/* Example usage. Quality is an int in the range [0, 100]
function example(quality){
// Pass in an existing image from the page
var theImg = document.getElementById('testimage');
// Use a canvas to extract the raw image data
var cvs = document.createElement('canvas');
cvs.width = theImg.width;
cvs.height = theImg.height;
var ctx = cvs.getContext("2d");
ctx.drawImage(theImg,0,0);
var theImgData = (ctx.getImageData(0, 0, cvs.width, cvs.height));
// Encode the image and get a URI back, toRaw is false by default
var jpegURI = encoder.encode(theImgData, quality);
var img = document.createElement('img');
img.src = jpegURI;
document.body.appendChild(img);
}
Example usage for getting back raw data and transforming it to a blob.
Raw data is useful when trying to send an image over XHR or Websocket,
it uses around 30% less bytes then a Base64 encoded string. It can
also be useful if you want to save the image to disk using a FileWriter.
NOTE: The browser you are using must support Blobs
function example(quality){
// Pass in an existing image from the page
var theImg = document.getElementById('testimage');
// Use a canvas to extract the raw image data
var cvs = document.createElement('canvas');
cvs.width = theImg.width;
cvs.height = theImg.height;
var ctx = cvs.getContext("2d");
ctx.drawImage(theImg,0,0);
var theImgData = (ctx.getImageData(0, 0, cvs.width, cvs.height));
// Encode the image and get a URI back, set toRaw to true
var rawData = encoder.encode(theImgData, quality, true);
blob = new Blob([rawData.buffer], {type: 'image/jpeg'});
var jpegURI = URL.createObjectURL(blob);
var img = document.createElement('img');
img.src = jpegURI;
document.body.appendChild(img);
}*/
### 前端H5基于canvas的图片压缩
#### 用法
```
var compressimg = require('compressimg');
compressimg(file, {
size: 640,
quality: 80,
cb: function(dataurl) {
var image = new Image();
image.src = dataurl;
}
});
```
- file 为从 input file 控件中选择的文件对象
- size 为源图压缩后长边的大小,短边按比例自动计算
- quality 为图片压缩质量
- cb 为压缩图片成功后的回调函数,参数`dataurl`为压缩后的图片的`data:image/jpeg,base64,...`
\ No newline at end of file
/* eslint-disable */
import cardutil from './cardWebsocket'
var readCard = {
// 获取设备
rdListCard: function(rdListCardcallback) {
if (cardutil.certWsStatus) {
cardutil.wsListCard(function(openCallback) {
// console.log('获取设备回调', openCallback)
// if (openCallback.err_code == 0) {
// readCard.rdConnectCard(rdListCardcallback);
// }
rdListCardcallback(openCallback)
})
} else {
readCard.rdReconnect('rdListCard', function(resp) {
rdListCardcallback(resp)
})
}
},
// 连接卡
rdConnectCard: function(rdConnectCardcallback) {
if (cardutil.certWsStatus) {
cardutil.wsConnectCard(function(resp) {
// console.log('连接卡回调', resp)
rdConnectCardcallback(resp)
})
} else {
readCard.rdReconnect('rdConnectCard', function(resp) {
rdConnectCardcallback(resp)
})
}
},
// 发送APDU
rdTransmitCard: function(Apdu, CardName, rdTransmitCard) {
if (cardutil.certWsStatus) {
cardutil.wsTransmitCard(Apdu, CardName, function(openCallback) {
// console.log('发送APDU回调', openCallback)
rdTransmitCard(openCallback)
})
} else {
// 连接ws
cardutil.startWebSocket(function(res) {
console.log('读证读卡ws链接状态1111', res)
if (res) {
cardutil.certWsStatus = true
readCard.rdTransmitCard(Apdu, CardName, rdTransmitCard)
} else {
cardutil.certWsStatus = false
var retcode = {
err_code: -1,
err_msg: 'ws连接失败,查看服务是否启动!!!',
data: ''
}
rdTransmitCard(retcode)
}
})
}
},
// 断开卡
rdDisconnectCard: function(rdDisconnectCardcallback) {
if (cardutil.certWsStatus) {
cardutil.wsDisconnectCard(function(openCallback) {
// console.log('断开卡回调', openCallback)
rdDisconnectCardcallback(openCallback)
})
} else {
readCard.rdReconnect('rdDisconnectCard', function(resp) {
rdDisconnectCardcallback(resp)
})
}
},
rdReconnect: function(params, rdReconnectCallback) {
// 连接ws
cardutil.startWebSocket(function(res) {
console.log('读证读卡ws链接状态1111', res)
if (res) {
cardutil.certWsStatus = true
if (params == 'rdListCard') {
readCard.rdListCard(rdReconnectCallback)
}
if (params == 'rdConnectCard') {
readCard.rdConnectCard(rdReconnectCallback)
}
if (params == 'rdDisconnectCard') {
readCard.rdDisconnectCard(rdReconnectCallback)
}
} else {
cardutil.certWsStatus = false
var retcode = {
err_code: -1,
err_msg: 'ws连接失败,查看服务是否启动!!!',
data: ''
}
rdReconnectCallback(retcode)
}
})
}
}
export default readCard
/* eslint-disable */
var clientId = '1234567890'
var device_sub_type = 0 // 0分离式,1一体式
var deviceName = 'SR236'
var deviceType = 0
var cardutil = {
certWsStatus: false,
// 主通道,用于打开设备、关闭设备等
websocket: null,
wsUrl: 'ws://127.0.0.1:35561/',
// 打开读证
openReadDeviceParms: {
cmd: 'open_device',
client_id: clientId,
device_name: 'Id_Card',
device_type: 4,
channel: 0,
device_sub_type: device_sub_type
},
// 设置秘钥
secretParams: {
cmd: 'set_app_param',
client_id: clientId,
device_name: 'Id_Card',
device_type: 4,
channel: 0,
device_sub_type: device_sub_type,
app_key: '',
app_secret: '',
app_pass_word: ''
},
// 读证
readIDCardParams: {
cmd: 'read_card_info',
client_id: clientId,
device_name: 'Id_Card',
device_type: 4,
channel: 0,
write_cmd: '',
timeout: 3
},
// 关闭读证
closeReadDeviceParams: {
cmd: 'close_device',
client_id: clientId,
device_name: 'Id_Card',
device_type: 4
},
wsListCardParams: { // 读卡获取设备
cmd: 'list_card',
client_id: clientId,
device_name: 'SR-CCID',
device_type: 5,
channel: 0
},
wsConnectCardParams: { // 连接卡
cmd: 'connect_card',
client_id: clientId,
device_name: 'SR-CCID',
device_type: 5,
channel: 0
},
wsTransmitCardParams: { // 发送APDU
cmd: 'transmit_card',
client_id: clientId,
device_name: 'Card',
device_type: 5,
channel: 0
},
wsDisconnectCardParams: { // 断开卡
cmd: 'disconnect_card',
client_id: clientId,
device_name: 'SR-CCID',
device_type: 5
},
// 连接回调
certConnectCallback: null, // 读证连接回调
readIDCardCallback: null, // 读取身份证回调
setAppParamExCallback: null, // 读证秘钥回调
readCardExCallback: null, // 读证分离式回调
wsListCardCallback: null, // 读卡获取设备回调
wsConnectCardCallback: null, // 连接卡回调
wsTransmitCardCallback: null, // 发送APDU回调
wsDisconnectCardCallback: null, // 断开卡回调
logMessage: function(message) {
if (typeof window.onHandleMessage !== 'undefined') { window.onHandleMessage(message) } else { console.log(message) }
},
// 连接主通道的websocket
startWebSocket: function(callback) {
cardutil.certConnectCallback = callback
if ('WebSocket' in window) {
cardutil.websocket = new WebSocket(cardutil.wsUrl)
} else if ('MozWebSocket' in window) {
cardutil.websocket = new MozWebSocket(cardutil.wsUrl)
} else {
window.alert('浏览器不支持WebSocket')
return
}
cardutil.websocket.binaryType = 'arraybuffer'
cardutil.websocket.onopen = function() {
console.log('Connected 主通道的URL: ', cardutil.wsUrl)
if (cardutil.websocket.readyState == 1) {
console.log('链接成功')
cardutil.certConnectCallback(true)
}
}
cardutil.websocket.onmessage = function(evt) {
cardutil.wsMessage(evt)
}
cardutil.websocket.onclose = function(evt) {
if (cardutil.websocket.readyState == 3) {
console.log('链接关闭', evt)
cardutil.certConnectCallback(false)
}
}
cardutil.websocket.onerror = function(evt) {
if (cardutil.websocket.readyState == 3) {
console.log('链接报错', evt)
cardutil.certConnectCallback(false)
}
}
},
// 发送信息
sendMsg: function(param) {
// console.log('发送信息', cardutil.websocket, param)
if (cardutil.websocket && param) {
cardutil.websocket.send(JSON.stringify(param))
}
},
// websocket主通道的数据返回
wsMessage: function(res) {
var retcode = {}
// console.log('收到返回的信息', res.data)
var res = JSON.parse(res.data)
var cmd = res.cmd
retcode.err_msg = res.message
switch (cmd) {
case 'open_device': // 打开读证
if (res.statuCode == 0) {
// 一体式读证
if (device_sub_type == 1) {
cardutil.sendMsg(cardutil.readIDCardParams)
} else { // 分离式读证
// 秘钥
cardutil.sendMsg(cardutil.secretParams)
}
} else {
cardutil.readIDCardCallback(retcode)
// 关闭读证
cardutil.sendMsg(cardutil.closeReadDeviceParams)
}
break
case 'read_card_info': // 读证
if (device_sub_type == 1) {
retcode.err_code = res.result
retcode.data = res.read_result
cardutil.readIDCardCallback(retcode)
} else {
retcode.resultFlag = res.result
retcode.status = ''
retcode.errorMsg = res.message
retcode.verderId = ''
retcode.resultContent = res.read_result
cardutil.readCardExCallback(retcode)
}
cardutil.sendMsg(cardutil.closeReadDeviceParams)
break
case 'close_device': // 关闭读证
// if (res.statuCode == 0) {
// textarea.value += '关闭读证' + res.message + "\r\n";
// } else {
// textarea.value += res.message + "\r\n";
// }
// cardutil.readIDCardCallback(res);
break
case 'set_app_param': // 设置秘钥
retcode.resultFlag = res.statuCode
retcode.errorMsg = res.message
cardutil.setAppParamExCallback(retcode)
// 关闭读证
cardutil.sendMsg(cardutil.closeReadDeviceParams)
break
case 'list_card': // 读卡获取设备
retcode.err_code = res.result
retcode.data = res.dev_name
cardutil.wsListCardCallback(retcode)
break
case 'connect_card': // 连接卡
retcode.err_code = res.statuCode
retcode.data = ''
cardutil.wsConnectCardCallback(retcode)
break
case 'transmit_card': // 发送APDU
retcode.err_code = res.result
retcode.data = res.read_result
cardutil.wsTransmitCardCallback(retcode)
break
case 'disconnect_card': // 断开卡
retcode.err_code = res.statuCode
retcode.data = ''
cardutil.wsDisconnectCardCallback(retcode)
break
}
},
// 断开检测服务器连接
cwStopWebSocket: function() {
if (cardutil.websocket) {
if (cardutil.websocket.readyState == 1) { cardutil.websocket.close() }
cardutil.websocket = null
return true
} else {
return false
}
},
// 服务连接出错
onSocketError: function(evt) {
cardutil.logMessage('连接检测服务有问题...')
},
// 服务连接关闭onSocketClose
onSocketClose: function(evt) {
// websocket = null;
cardutil.logMessage('服务已断开...')
},
// 读取身份证(一体式)
wsReadIntegratedCard: function(callback) {
device_sub_type = 1
cardutil.openReadDeviceParms.device_sub_type = 1
cardutil.readIDCardCallback = callback
cardutil.sendMsg(cardutil.openReadDeviceParms)
},
// 设置秘钥
setAppParamEx: function(appKey, appSecret, password, callback) {
device_sub_type = 0
cardutil.secretParams.device_sub_type = 0
cardutil.openReadDeviceParms.device_sub_type = 0
cardutil.secretParams.app_key = appKey
cardutil.secretParams.app_secret = appSecret
cardutil.secretParams.app_pass_word = password
cardutil.setAppParamExCallback = callback
cardutil.sendMsg(cardutil.openReadDeviceParms)
},
// 读证分离式
readCardEx: function(callback) {
cardutil.readCardExCallback = callback
cardutil.sendMsg(cardutil.readIDCardParams)
},
/** 读卡**/
// 获取设备
wsListCard: function(callback) {
cardutil.wsListCardCallback = callback
cardutil.sendMsg(cardutil.wsListCardParams)
},
// 连接卡
wsConnectCard: function(callback) {
cardutil.wsConnectCardCallback = callback
cardutil.sendMsg(cardutil.wsConnectCardParams)
},
// 发送APDU
wsTransmitCard: function(Apdu, CardName, callback) {
cardutil.wsTransmitCardParams.apdu_cmd = Apdu
cardutil.wsTransmitCardParams.device_name = CardName
cardutil.wsTransmitCardCallback = callback
cardutil.sendMsg(cardutil.wsTransmitCardParams)
},
// 断开卡
wsDisconnectCard: function(callback) {
cardutil.wsDisconnectCardCallback = callback
cardutil.sendMsg(cardutil.wsDisconnectCardParams)
}
}
export default cardutil
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>ico_双箭头_未点亮@2x</title>
<g id="车企实名制系统" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="企业实名-责任人信息" transform="translate(-1021.000000, -208.000000)" fill="#EDEEF0">
<g id="编组-9" transform="translate(240.000000, 180.000000)">
<g id="编组-7" transform="translate(781.000000, 24.000000)">
<g id="编组-6" transform="translate(0.000000, 4.000000)">
<path d="M2.7464194,2.87364782 L5.973,6.068 L6.02782537,6.11447931 L6.45207911,6.53873305 C6.55548349,6.64315249 6.61241949,6.77274527 6.62509311,6.90571855 C6.64247636,7.07758454 6.58575011,7.25647191 6.45416723,7.38935969 L6.45207911,7.39145814 L6.02782537,7.81571188 L5.973,7.861 L2.7464194,11.0565434 C2.53784454,11.2630701 2.21577734,11.2852802 1.98278909,11.1236733 L1.89998972,11.0544552 L1.47364785,10.628103 C1.2663984,10.4187983 1.24485169,10.0953788 1.40799874,9.86233915 L1.47783443,9.77958524 L4.32,6.964 L1.47783443,4.15060594 C1.24236661,3.91745031 1.24049222,3.53755595 1.47364785,3.30208813 C1.47434217,3.30138693 1.47503821,3.30068745 1.47573598,3.29998968 L1.89998972,2.87573594 C2.13348723,2.64223843 2.51177268,2.64130521 2.7464194,2.87364782 Z M8.7464194,2.87364782 L11.973,6.068 L12.0278254,6.11447931 L12.4520791,6.53873305 C12.5554835,6.64315249 12.6124195,6.77274527 12.6250931,6.90571855 C12.6424764,7.07758454 12.5857501,7.25647191 12.4541672,7.38935969 L12.4520791,7.39145814 L12.0278254,7.81571188 L11.973,7.861 L8.7464194,11.0565434 C8.53784454,11.2630701 8.21577734,11.2852802 7.98278909,11.1236733 L7.89998972,11.0544552 L7.47364785,10.628103 C7.2663984,10.4187983 7.24485169,10.0953788 7.40799874,9.86233915 L7.47783443,9.77958524 L10.32,6.964 L7.47783443,4.15060594 C7.24236661,3.91745031 7.24049222,3.53755595 7.47364785,3.30208813 C7.47434217,3.30138693 7.47503821,3.30068745 7.47573598,3.29998968 L7.89998972,2.87573594 C8.13348723,2.64223843 8.51177268,2.64130521 8.7464194,2.87364782 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>ico_双箭头_未点亮@2x</title>
<g id="车企实名制系统" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="企业实名-责任人信息" transform="translate(-755.000000, -208.000000)" fill="#212026">
<g id="编组-9" transform="translate(240.000000, 180.000000)">
<g id="编组-7备份" transform="translate(515.000000, 24.000000)">
<g id="编组-6" transform="translate(0.000000, 4.000000)">
<path d="M2.7464194,2.87364782 L5.973,6.068 L6.02782537,6.11447931 L6.45207911,6.53873305 C6.55548349,6.64315249 6.61241949,6.77274527 6.62509311,6.90571855 C6.64247636,7.07758454 6.58575011,7.25647191 6.45416723,7.38935969 L6.45207911,7.39145814 L6.02782537,7.81571188 L5.973,7.861 L2.7464194,11.0565434 C2.53784454,11.2630701 2.21577734,11.2852802 1.98278909,11.1236733 L1.89998972,11.0544552 L1.47364785,10.628103 C1.2663984,10.4187983 1.24485169,10.0953788 1.40799874,9.86233915 L1.47783443,9.77958524 L4.32,6.964 L1.47783443,4.15060594 C1.24236661,3.91745031 1.24049222,3.53755595 1.47364785,3.30208813 C1.47434217,3.30138693 1.47503821,3.30068745 1.47573598,3.29998968 L1.89998972,2.87573594 C2.13348723,2.64223843 2.51177268,2.64130521 2.7464194,2.87364782 Z M8.7464194,2.87364782 L11.973,6.068 L12.0278254,6.11447931 L12.4520791,6.53873305 C12.5554835,6.64315249 12.6124195,6.77274527 12.6250931,6.90571855 C12.6424764,7.07758454 12.5857501,7.25647191 12.4541672,7.38935969 L12.4520791,7.39145814 L12.0278254,7.81571188 L11.973,7.861 L8.7464194,11.0565434 C8.53784454,11.2630701 8.21577734,11.2852802 7.98278909,11.1236733 L7.89998972,11.0544552 L7.47364785,10.628103 C7.2663984,10.4187983 7.24485169,10.0953788 7.40799874,9.86233915 L7.47783443,9.77958524 L10.32,6.964 L7.47783443,4.15060594 C7.24236661,3.91745031 7.24049222,3.53755595 7.47364785,3.30208813 C7.47434217,3.30138693 7.47503821,3.30068745 7.47573598,3.29998968 L7.89998972,2.87573594 C8.13348723,2.64223843 8.51177268,2.64130521 8.7464194,2.87364782 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="17px" viewBox="0 0 16 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>icon_相机</title>
<g id="车企实名制系统" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="入网协议-拍照" transform="translate(-945.000000, -236.000000)">
<g id="编组-7" transform="translate(407.000000, 150.000000)">
<g id="Group-52" transform="translate(522.000000, 78.000000)">
<g id="icon_相机" transform="translate(16.000000, 8.000000)">
<rect id="矩形" x="0" y="0.5" width="16" height="16"></rect>
<polygon id="路径" stroke="#2A68FF" stroke-width="2" fill="#2A68FF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round" points="2 13.3125 2 5.34375 5.27272727 5.34375 6.36363636 3.75 9.63636364 3.75 10.7272727 5.34375 14 5.34375 14 13.3125"></polygon>
<path d="M8,11.71875 C9.242675,11.71875 10.25,10.6484672 10.25,9.328125 C10.25,8.00778281 9.242675,6.9375 8,6.9375 C6.757325,6.9375 5.75,8.00778281 5.75,9.328125 C5.75,10.6484672 6.757325,11.71875 8,11.71875 Z" id="路径" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment