Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
聂康
cusc-realname-private-pc-master
Commits
d2c4018b
Commit
d2c4018b
authored
Jun 17, 2025
by
kang.nie@inzymeits.com
Browse files
初始化代码
parent
de8a8ae5
Pipeline
#3105
failed with stages
in 0 seconds
Changes
393
Pipelines
1
Show whitespace changes
Inline
Side-by-side
src/components/CompFaceRecognition/index.vue
0 → 100644
View file @
d2c4018b
<
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
>
src/components/CompFileInfo/index.vue
0 → 100644
View file @
d2c4018b
<
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
>
src/components/CompFilePreview/file.vue
0 → 100644
View file @
d2c4018b
<
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
>
src/components/CompFilePreview/index.vue
0 → 100644
View file @
d2c4018b
<
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
>
src/components/CompMasterFileInfo/index.vue
0 → 100644
View file @
d2c4018b
<
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
>
src/components/CompScanCode/index.vue
0 → 100644
View file @
d2c4018b
<
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
>
src/components/CompSign/index.vue
0 → 100644
View file @
d2c4018b
<
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
>
src/components/CompVin/index.vue
0 → 100644
View file @
d2c4018b
<
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
>
src/components/CompVinCard/index.vue
0 → 100644
View file @
d2c4018b
<
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
>
src/components/CompressImg/blobutil/index.js
0 → 100644
View file @
d2c4018b
/* 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
};
src/components/CompressImg/exif/index.js
0 → 100644
View file @
d2c4018b
/* 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
src/components/CompressImg/imagemime/index.js
0 → 100644
View file @
d2c4018b
/* 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
;
src/components/CompressImg/index.js
0 → 100644
View file @
d2c4018b
/* 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
src/components/CompressImg/jpegencoder/index.js
0 → 100644
View file @
d2c4018b
/* 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);
}*/
src/components/CompressImg/readme.md
0 → 100644
View file @
d2c4018b
### 前端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
src/components/Device/card.js
0 → 100644
View file @
d2c4018b
/* 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
src/components/Device/cardWebsocket.js
0 → 100644
View file @
d2c4018b
/* 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
src/components/Device/components/photoStep/icon_arrow.svg
0 → 100644
View file @
d2c4018b
<?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
src/components/Device/components/photoStep/icon_arrow_finished.svg
0 → 100644
View file @
d2c4018b
<?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
src/components/Device/components/photoStep/icon_camera.svg
0 → 100644
View file @
d2c4018b
<?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
Prev
1
…
6
7
8
9
10
11
12
13
14
…
20
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment