init source code

This commit is contained in:
makeit 2024-08-07 15:40:51 +08:00
parent 5db52f125f
commit 2c79524599
12 changed files with 343 additions and 14 deletions

31
.gitignore vendored
View File

@ -1,14 +1,21 @@
# ---> VisualStudioCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
node_modules
.DS_Store
dist
*.local
package-lock.json
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.history
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

21
LICENSE Normal file
View 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.

View File

@ -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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

150
src/App.vue Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,10 @@
import { SharedConfig } from "vite";
const config: SharedConfig = {
cssPreprocessOptions: {
less: {
javascriptEnabled: true
}
}
}
export default config