feat: 刷赞增加扫码功能,方便猪猪使用

This commit is contained in:
539943419 committed 2025-01-21 13:59:58 +08:00
1 parent 72ba1691fb
commit 741a05c9b6
5 files changed
+185 -5

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
<template>
<div class="qr-scanner-wrapper">
<video ref="scannerDisplayRef" autoplay></video>
<canvas ref="testCtx" v-show="false"></canvas>
</div>
</template>
<script setup lang="ts">
import jsQR from 'jsqr'
const emit = defineEmits<{
onResult: [value: string]
}>()
const scannerDisplayRef = ref<HTMLVideoElement>()
const testCtx = ref<HTMLCanvasElement>()
const result = reactive<string[]>([])
let stream: MediaStream | null = null
let timer: any | undefined = undefined
const scannerResult = new Set()
const getQRData = (videoFrame: VideoFrame): string | undefined => {
const canvas = document.createElement('canvas')
// const canvas = testCtx.value!
// @ts-ignore
canvas.width = videoFrame.width
// @ts-ignore
canvas.height = videoFrame.height
// console.log(canvas.width, canvas.height)
const ctx = canvas.getContext('2d')
ctx?.drawImage(videoFrame, 0, 0, canvas.width, canvas.height)
const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height)
if (!imageData) return undefined
const code = jsQR(imageData?.data, imageData?.width, imageData?.height)
return code?.data
}
const handleVideoTrack = (track: MediaStreamTrack) => {
timer && clearInterval(timer);
let errCount = 0
// @ts-ignore
const imageCapture = new ImageCapture(track)
timer = setInterval(() => {
try {
imageCapture.grabFrame().then((videoFrame: VideoFrame) => {
const data = getQRData(videoFrame)
if (data &&!scannerResult.has(data)) {
console.log(data)
scannerResult.add(data)
result.push(data)
emit('onResult', data)
}
videoFrame.close()
})
} catch (e) {
errCount++
if (errCount > 10) {
clearInterval(timer)
}
}
}, 500)
}
onMounted(async () => {
try {
stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' },
})
} catch (e) {
stream = await navigator.mediaDevices.getDisplayMedia({
video: true,
})
}
if (stream) {
scannerDisplayRef.value!.srcObject = stream
const videoTrack = stream.getVideoTracks()[0]
handleVideoTrack(videoTrack)
}
})
onBeforeUnmount(() => {
stream?.getTracks().forEach(track => track.stop())
})
</script>
<style scoped>
.qr-scanner-wrapper {
display: flex;
justify-content: center;
align-items: center;
video {
height: inherit;
width: inherit;
object-fit: contain;
}
}
</style>
+1
View File
@@ -19,6 +19,7 @@
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jsqr": "^1.4.0",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"nuxt": "^3.15.1", "nuxt": "^3.15.1",
+19
View File
@@ -0,0 +1,19 @@
<template>
<div class="qr-scanner-wrapper">
<miaoQRScanner></miaoQRScanner>
</div>
</template>
<script setup lang="ts">
import miaoQRScanner from '~/components/miaoQRScanner.vue';
</script>
<style scoped>
.qr-scanner-wrapper {
width: 100%;
height: calc(100vh - 60px);
display: flex;
justify-content: center;
align-items: center;
}
</style>
+56 -5
View File
@@ -4,11 +4,17 @@
<el-divider></el-divider> <el-divider></el-divider>
<main class="fuck-sq-main"> <main class="fuck-sq-main">
<el-row style="width: 100%;" :gutter="20"> <el-row style="width: 100%;" :gutter="20">
<el-col :span="24"> <el-col :span="20">
<el-input v-model="url"> <el-input v-model="url">
<template #prepend>链接</template> <template #prepend>链接</template>
</el-input> </el-input>
</el-col> </el-col>
<!-- <el-col :span="4">
<el-button type="primary" style="width: 100%" @click="handleSelectImage">图片</el-button>
</el-col> -->
<el-col :span="4">
<el-button type="primary" style="width: 100%" @click="handleOpenScanner">扫码</el-button>
</el-col>
</el-row> </el-row>
<el-row style="width: 100%;" :gutter="20"> <el-row style="width: 100%;" :gutter="20">
<el-col :span="13" class="col-flex-end"> <el-col :span="13" class="col-flex-end">
@@ -35,15 +41,22 @@
</el-scrollbar> </el-scrollbar>
</div> </div>
</main> </main>
<div class="qr-scanner" v-if="displayQRScanner" @click="displayQRScanner = false">
<miaoQRScanner class="qr-scanner-scanner" @on-result="handleScannerRes" @click.stop></miaoQRScanner>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const router = useRouter()
const url = ref('') const url = ref('')
const likes = ref(10) const likes = ref(10)
const status = ref<'waiting' | 'busy'>('waiting') const status = ref<'waiting' | 'busy'>('waiting')
const displayQRScanner = ref(false)
const messages = reactive<{ const messages = reactive<{
timestamp: number, timestamp: number,
message: string, message: string,
@@ -58,10 +71,14 @@ const log = (...message: string[]) => {
}) })
} }
const id = computed(() => { const matchId = (url: string) => {
const idMatch0 = url.value.match(/id%3D(\d+)/); const idMatch0 = url.match(/id%3D(\d+)/);
const idMatch1 = url.value.match(/id=(\d+)/); const idMatch1 = url.match(/id=(\d+)/);
return idMatch0? idMatch0[1] : idMatch1? idMatch1[1] : undefined return idMatch0? idMatch0[1] : idMatch1? idMatch1[1] : undefined
}
const id = computed(() => {
return matchId(url.value)
}) })
const _fd = ( const _fd = (
@@ -101,6 +118,23 @@ const handleClear = () => {
messages.splice(0, messages.length) messages.splice(0, messages.length)
} }
const handleOpenScanner = () => {
// router.push('/tools/QRScanner')
displayQRScanner.value = true
}
const handleScannerRes = (res: string) => {
const id = matchId(res)
if(id) {
url.value = res
}
displayQRScanner.value = false
}
const handleSelectImage = () => {
}
watchEffect(() => { watchEffect(() => {
if (id.value) { if (id.value) {
log(`识别到id: ${id.value}`) log(`识别到id: ${id.value}`)
@@ -111,7 +145,7 @@ watchEffect(() => {
</script> </script>
<style scoped> <style lang="scss" scoped>
.fuck-sq-wrapper { .fuck-sq-wrapper {
width: 95%; width: 95%;
height: 100%; height: 100%;
@@ -162,4 +196,21 @@ watchEffect(() => {
width: 95%; width: 95%;
margin: 0 10px; margin: 0 10px;
} }
.qr-scanner {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
&-scanner {
width: 80%;
height: 80%;
object-fit: contain;
}
}
</style> </style>
+8
View File
@@ -35,6 +35,9 @@ importers:
jsonwebtoken: jsonwebtoken:
specifier: ^9.0.2 specifier: ^9.0.2
version: 9.0.2 version: 9.0.2
jsqr:
specifier: ^1.4.0
version: 1.4.0
markdown-it: markdown-it:
specifier: ^14.1.0 specifier: ^14.1.0
version: 14.1.0 version: 14.1.0
@@ -2174,6 +2177,9 @@ packages:
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
engines: {node: '>=12', npm: '>=6'} engines: {node: '>=12', npm: '>=6'}
jsqr@1.4.0:
resolution: {integrity: sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==}
jwa@1.4.1: jwa@1.4.1:
resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
@@ -5944,6 +5950,8 @@ snapshots:
ms: 2.1.3 ms: 2.1.3
semver: 7.6.3 semver: 7.6.3
jsqr@1.4.0: {}
jwa@1.4.1: jwa@1.4.1:
dependencies: dependencies:
buffer-equal-constant-time: 1.0.1 buffer-equal-constant-time: 1.0.1