init source code
This commit is contained in:
parent
5db52f125f
commit
2c79524599
31
.gitignore
vendored
31
.gitignore
vendored
@ -1,14 +1,21 @@
|
|||||||
# ---> VisualStudioCode
|
node_modules
|
||||||
.vscode/*
|
.DS_Store
|
||||||
!.vscode/settings.json
|
dist
|
||||||
!.vscode/tasks.json
|
*.local
|
||||||
!.vscode/launch.json
|
package-lock.json
|
||||||
!.vscode/extensions.json
|
|
||||||
!.vscode/*.code-snippets
|
|
||||||
|
|
||||||
# Local History for Visual Studio Code
|
# Log files
|
||||||
.history/
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
# Built Visual Studio Code Extensions
|
yarn-error.log*
|
||||||
*.vsix
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.history
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 makeit.vip
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
16
README.md
16
README.md
@ -1,3 +1,15 @@
|
|||||||
# makeit-scan
|
# 扫描解析二维码 / 条形码
|
||||||
|
> 整个 Demo 项目采用的是 Vue3 + Vite + zxing + Ant Design Vue 构建。实现 Web 浏览器端拉起摄像头,扫描二维码 / 条形码并对其解析的功能。(注意:iPhone 系列的手机,除了 Safari 自带浏览器外,其它任何的浏览器是没有权限配置网站是否可用摄像头的,所以采用折中的解决方案,拉起摄像头后拍照进行识别)。
|
||||||
|
|
||||||
:sparkling_heart: H5 + Web 浏览器端拉起摄像头,实现二维码 / 条形码的扫描并且解析(iPhone 系列手机,除了 Safari 自带浏览器支持外,其它任何浏览器不支持设置网站权限,采用折中的拍照识别方式,识别正确率比较低):boom: 注:并未做比较全面的测试 :stuck_out_tongue_winking_eye:
|
> Demo 体验地址:[https://scan.makeit.vip/](https://scan.makeit.vip/)
|
||||||
|
|
||||||
|
## 证书
|
||||||
|
> `localhost` 支持调试,需要用其它任何域名来调试的话,先配置 `https`(我测试的时候用的是 `letsencrypt` + `docker` 来生成免费的 `https` 证书的)
|
||||||
|
|
||||||
|
## 运行
|
||||||
|
```
|
||||||
|
git clone https://github.com/lirongtong/miitvip-canvas-demo.git
|
||||||
|
cd miitvip-scan-demo
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
16
index.html
Normal file
16
index.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="renderer" content="webkit|ie-comp|ie-stand">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<title>H5 二维码 / 条形码扫描解析 - Powered By makeit.vip.</title>
|
||||||
|
<meta name="description" content="H5 + Web 浏览器端拉起摄像头,实现二维码 / 条形码的扫描并且解析(iPhone 系列手机,除了 Safari 自带浏览器支持外,其它任何浏览器不支持设置网站权限,采用折中的拍照识别方式,识别正确率比较低)注:并未做比较全面的测试">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/app.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
25
package.json
Normal file
25
package.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "miitvip-scan-demo",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"author": {
|
||||||
|
"url": "https://www.makeit.vip",
|
||||||
|
"name": "lirongtong",
|
||||||
|
"email": "lirongtong@hotmail.com",
|
||||||
|
"github": "https://github.com/lirongtong"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@zxing/library": "^0.18.3",
|
||||||
|
"ant-design-vue": "^2.0.0-rc.8",
|
||||||
|
"vue": "^3.0.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/compiler-sfc": "^3.0.5",
|
||||||
|
"less": "^3.12.2",
|
||||||
|
"less-loader": "^7.0.2",
|
||||||
|
"vite": "^1.0.0-rc.13"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
150
src/App.vue
Normal file
150
src/App.vue
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<template>
|
||||||
|
<a href="https://www.makeit.vip" target="_blank"><img class="logo" alt="makeit.vip's logo" src="https://file.makeit.vip/MIITVIP/M00/00/00/K4vDRGPcbmmAG8_sAAAtlj6Tt_s562.png" /></a>
|
||||||
|
<video class="video" id="video" ref="video" autoplay v-if="!iphone"></video>
|
||||||
|
<div class="photograph" v-if="iphone">
|
||||||
|
<a-button type="danger" size="large">拍照 - 扫描解析二维码 / 条形码</a-button>
|
||||||
|
<input class="file" type="file" ref="camera" capture="camera" accept="image/*" @change="change" />
|
||||||
|
<img id="preview" alt="preview" v-if="preview" />
|
||||||
|
</div>
|
||||||
|
<a-modal v-model:visible="modalVisible" centered title="温馨提示:扫描解析成功" okText="刷新后再次进行识别操作" cancelText="关闭" @ok="reload" @cancel="cancel">
|
||||||
|
<p>扫描结果:<a :href="content" target="_blank">{{ content }}</a></p>
|
||||||
|
<p>扫描时间:{{ time }}</p>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref } from 'vue'
|
||||||
|
import { BrowserMultiFormatReader } from '@zxing/library'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const iphone = ref(false)
|
||||||
|
const errMsg = ref('')
|
||||||
|
const time = ref(null)
|
||||||
|
const content = ref(null)
|
||||||
|
const preview = ref(false)
|
||||||
|
const modalVisible = ref(false)
|
||||||
|
const reader = new BrowserMultiFormatReader()
|
||||||
|
return {iphone, errMsg, time, content, preview, modalVisible, reader}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async openCamera() {
|
||||||
|
this.$message.success({
|
||||||
|
content: '正在尝试拉起摄像头 ...',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
if (!navigator.mediaDevices) {
|
||||||
|
this.$message.destroy()
|
||||||
|
this.iphone = true
|
||||||
|
this.$message.success({
|
||||||
|
content: 'iPhone 其它浏览器无权限自动开启摄像头 ...',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.reader.listVideoInputDevices().then((devices) => {
|
||||||
|
if (devices.length <= 0) {
|
||||||
|
this.$message.destroy();
|
||||||
|
this.$message.warning({
|
||||||
|
content: '当前没有可用的摄像头设备 ...',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let id = devices[0].deviceId
|
||||||
|
for (let i = 0; i < devices.length; i++) {
|
||||||
|
if (
|
||||||
|
devices[i].label.indexOf('back') !== -1 ||
|
||||||
|
devices[i].label.indexOf('RGB') !== -1
|
||||||
|
) {
|
||||||
|
id = devices[i].deviceId
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.decode(id)
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
this.errMsg = err
|
||||||
|
this.$message.destroy()
|
||||||
|
this.$message.error({
|
||||||
|
content: err,
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
decode(id: any) {
|
||||||
|
this.reader.reset()
|
||||||
|
this.$message.destroy()
|
||||||
|
this.$message.success({
|
||||||
|
content: '正在尝试识别,请对准摄像头 ...',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
this.reader.decodeOnceFromVideoDevice(id, 'video').then((res) => {
|
||||||
|
this.$message.destroy()
|
||||||
|
this.content = res.text
|
||||||
|
this.time = new Date(res.timestamp)
|
||||||
|
this.modalVisible = true
|
||||||
|
}).catch((err) => {
|
||||||
|
this.$message.destroy()
|
||||||
|
this.$message.error({
|
||||||
|
content: '识别失败,请刷新后再次尝试 ...',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
this.errMsg = err
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
decodeFromImage() {
|
||||||
|
const image = document.getElementById('preview') as HTMLImageElement
|
||||||
|
this.reader.decodeFromImage(image).then((res) => {
|
||||||
|
this.$message.destroy()
|
||||||
|
this.content = res.text
|
||||||
|
this.time = new Date(res.timestamp)
|
||||||
|
this.modalVisible = true
|
||||||
|
}).catch((err) => {
|
||||||
|
this.$message.destroy()
|
||||||
|
this.$message.error({
|
||||||
|
content: '识别失败,请刷新后再试',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
this.errMsg = err
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
change() {
|
||||||
|
const vm = this
|
||||||
|
vm.$message.success({
|
||||||
|
content: '正在识别图片,请稍候 ...',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
const files = (this.$refs.camera as any).files
|
||||||
|
const file = files[files.length - 1]
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = (e: any) => {
|
||||||
|
this.preview = true
|
||||||
|
vm.$nextTick(() => {
|
||||||
|
const image = document.getElementById('preview') as HTMLInputElement
|
||||||
|
if (image) image.src = e.target.result
|
||||||
|
this.decodeFromImage()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
},
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
window.location.reload()
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.$message.success({
|
||||||
|
content: '上次扫描解析成功,请刷新后再次尝试识别 ...',
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {this.openCamera()}
|
||||||
|
})
|
||||||
|
</script>
|
6
src/app.ts
Normal file
6
src/app.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import {createApp} from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import antd from './modules'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
createApp(App).use(antd).mount('#app')
|
61
src/index.css
Normal file
61
src/index.css
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 120px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video {
|
||||||
|
width: 320px;
|
||||||
|
border: 1px solid #dbdbdb;
|
||||||
|
margin: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-title,
|
||||||
|
.ant-modal-body p {
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photograph {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
border-radius: 4px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 290px;
|
||||||
|
height: 40px;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview {
|
||||||
|
width: 320px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
16
src/modules.ts
Normal file
16
src/modules.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { App } from 'vue'
|
||||||
|
import 'ant-design-vue/lib/message/style/index.less'
|
||||||
|
import 'ant-design-vue/lib/modal/style/index.less'
|
||||||
|
import 'ant-design-vue/lib/button/style/index.less'
|
||||||
|
import { message, Modal, Button } from 'ant-design-vue'
|
||||||
|
|
||||||
|
const components = { message, Modal, Button } as any
|
||||||
|
const antd = {
|
||||||
|
install(app: App) {
|
||||||
|
app.config.globalProperties.$message = message
|
||||||
|
Object.keys(components).forEach((name) => {
|
||||||
|
app.use(components[name])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default antd
|
5
src/shims.d.ts
vendored
Normal file
5
src/shims.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
declare module '*.vue' {
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
const component: ReturnType<typeof defineComponent>
|
||||||
|
export default component
|
||||||
|
}
|
10
vite.config.ts
Normal file
10
vite.config.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { SharedConfig } from "vite";
|
||||||
|
|
||||||
|
const config: SharedConfig = {
|
||||||
|
cssPreprocessOptions: {
|
||||||
|
less: {
|
||||||
|
javascriptEnabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default config
|
Loading…
Reference in New Issue
Block a user