feat: 刷赞添加图片识别二维码功能

This commit is contained in:
539943419 committed 2025-01-21 19:14:40 +08:00
1 parent f3af29f04c
commit f8fba79d55
4 files changed
+168 -31

No files matched your search

+2
View File
@@ -26,3 +26,5 @@ logs
.pnpm-lock.yaml
userData
test/*
+12 -11
View File
@@ -42,18 +42,17 @@ const handleVideoTrack = (track: MediaStreamTrack) => {
let errCount = 0
// @ts-ignore
const imageCapture = new ImageCapture(track)
timer = setInterval(() => {
timer = setInterval(async () => {
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()
})
const videoFrame = await imageCapture.grabFrame()
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) {
@@ -96,6 +95,8 @@ onBeforeUnmount(() => {
video {
height: inherit;
width: inherit;
max-height: inherit;
max-width: inherit;
object-fit: contain;
}
}
+49 -20
View File
@@ -4,14 +4,14 @@
<el-divider></el-divider>
<main class="fuck-sq-main">
<el-row style="width: 100%;" :gutter="20">
<el-col :span="20">
<el-col :span="16">
<el-input v-model="url">
<template #prepend>链接</template>
</el-input>
</el-col>
<!-- <el-col :span="4">
<el-col :span="4">
<el-button type="primary" style="width: 100%" @click="handleSelectImage">图片</el-button>
</el-col> -->
</el-col>
<el-col :span="4">
<el-button type="primary" style="width: 100%" @click="handleOpenScanner">扫码</el-button>
</el-col>
@@ -22,11 +22,12 @@
<el-input-number style="width: 130px;" v-model="likes" :min="1" :max="300" />
</el-col>
<el-col :span="4" class="col-flex-end">
<el-button type="primary" style="width: 100%"
:disabled="status === 'busy' || id === undefined" @click="handleStart">开始</el-button>
<el-button type="primary" style="width: 100%" :disabled="status === 'busy' || id === undefined"
@click="handleStart">开始</el-button>
</el-col>
<el-col :span="7" class="col-flex-end">
<el-button type="primary" style="width: 100%" :disabled="messages.length === 0" @click="handleClear">清空输出</el-button>
<el-button type="primary" style="width: 100%" :disabled="messages.length === 0"
@click="handleClear">清空输出</el-button>
</el-col>
</el-row>
<div class="message-container-wrapper">
@@ -48,8 +49,11 @@
</template>
<script setup lang="ts">
import jsQR from 'jsqr'
const router = useRouter()
// const router = useRouter()
const testCRef = ref<HTMLCanvasElement>()
const url = ref('')
const likes = ref(10)
@@ -74,7 +78,7 @@ const log = (...message: string[]) => {
const matchId = (url: string) => {
const idMatch0 = url.match(/id%3D(\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(() => {
@@ -94,12 +98,12 @@ const handleStart = () => {
};
sse.onmessage = function (event) {
const data = JSON.parse(event.data.split('data: ')[1])
if(data.likeCountBefore || data.likeCountAfter) {
if (data.likeCountBefore || data.likeCountAfter) {
log(`当前赞数: ${data?.likeCountBefore ?? data?.likeCountAfter}`)
} else if(data.process) {
} else if (data.process) {
log(`已点赞 ${data.process} 次,已完成 ${data.percent}%`)
}
if(data.likeCountAfter) {
if (data.likeCountAfter) {
sse.close()
log('任务完成')
status.value = 'waiting'
@@ -125,14 +129,37 @@ const handleOpenScanner = () => {
const handleScannerRes = (res: string) => {
const id = matchId(res)
if(id) {
if (id) {
url.value = res
}
displayQRScanner.value = false
}
const handleSelectImage = () => {
const handleSelectImage = async () => {
const fper = new FilePicker()
const files = await fper.pick({
accept: 'image/*',
multiple: false
})
const file = files[0]
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')!
const img = await createImageBitmap(file)
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0, img.width, img.height)
const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height)
if (!imageData) return
const code = jsQR(imageData?.data, imageData?.width, imageData?.height)
if(code?.data) {
const _url = code.data
const id = matchId(_url)
if(id) {
url.value = _url
} else {
log('图片未识别到id')
}
}
}
watchEffect(() => {
@@ -184,15 +211,15 @@ watchEffect(() => {
.message-container {
width: 100%;
max-width: 800px;
height: calc( 100vh - 350px );
max-height: calc( 100vh - 350px );
height: calc(100vh - 350px);
max-height: calc(100vh - 350px);
overflow-y: auto;
display: flex;
flex-direction: column;
padding: 10px 0 ;
padding: 10px 0;
}
.message-container-message {
.message-container-message {
width: 95%;
margin: 0 10px;
}
@@ -208,9 +235,11 @@ watchEffect(() => {
justify-content: center;
align-items: center;
z-index: 3000;
&-scanner {
// width: 80%;
height: 80%;
max-width: 80vw;
max-height: 80vh;
// height: 80%;
object-fit: contain;
}
}
+105
View File
@@ -0,0 +1,105 @@
export class FilePicker {
private _resolve: ((files: File[]) => void) | undefined
constructor() {
if (this._resolve) {
this._resolve([])
this._resolve = undefined
}
}
public pick(option?: { accept?: string; multiple?: boolean }) {
if (this._resolve) {
this._resolve([])
this._resolve = undefined
}
return new Promise<File[]>((resolve) => {
const inpEle = document.createElement('input')
inpEle.id = `__file_${Math.ceil(Math.random() * 100000000)}`
inpEle.type = 'file'
inpEle.style.display = 'none'
this._resolve = resolve
// 文件类型限制
// inpEle.accept="image/*"
option?.accept && (inpEle.accept = option?.accept)
// 多选限制
option?.multiple && (inpEle.multiple = option?.multiple)
// inpEle.addEventListener("change", event => fn.call(inpEle, event, inpEle.files), { once: true });
inpEle.addEventListener(
'change',
() => {
if (!inpEle.files) {
return
}
const files: File[] = []
let i = 0
while (i < inpEle.files?.length) {
const _file = inpEle?.files?.item(i)
if (_file!== null) {
files.push(_file)
}
i += 1
}
resolve(files)
this._resolve = undefined
},
{ once: true }
)
inpEle.addEventListener('abort', () => {
this._resolve && this._resolve([])
this._resolve = undefined
})
inpEle.addEventListener('error', () => {
this._resolve && this._resolve([])
this._resolve = undefined
})
inpEle.click()
})
}
}
// (() => {
// let _resolve: ((files: File[]) => void) | undefined
// return (option?: { accept?: 'string'; multiple: boolean }) => {
// if (_resolve) {
// _resolve([])
// _resolve = undefined
// }
// return new Promise<File[]>((resolve) => {
// const inpEle = document.createElement('input')
// inpEle.id = `__file_${Math.ceil(Math.random() * 100000000)}`
// inpEle.type = 'file'
// inpEle.style.display = 'none'
// _resolve = resolve
// // 文件类型限制
// // inpEle.accept="image/*"
// option?.accept && (inpEle.accept = option?.accept)
// // 多选限制
// option?.multiple && (inpEle.multiple = option?.multiple)
// // inpEle.addEventListener("change", event => fn.call(inpEle, event, inpEle.files), { once: true });
// inpEle.addEventListener(
// 'change',
// () => {
// if (!inpEle.files) {
// return
// }
// const files: File[] = []
// let i = 0
// while (i < inpEle.files?.length) {
// const _file = inpEle?.files?.item(i)
// if (_file !== null) {
// files.push(_file)
// }
// i += 1
// }
// resolve(files)
// _resolve = undefined
// },
// { once: true }
// )
// inpEle.click()
// })
// }
// })()