使用zxing實現掃碼功能
1.安裝zxing
"devDependencies": { "@zxing/library": "^0.21.3", }
2.手機端頁面使用示例(該掃碼功能也可用于PC端掃碼)
<template>
<div :class="subFormItem ? 'sub-comp' : 'custom-comp'">
<center>
<van-icon name="scan" class="scan-icon" @click="startScan" />
<div>{{ item.placeholder }}</div>
</center>
<!-- 添加掃碼界面容器 -->
<div v-if="isScanning" class="scan-container">
<div class="scan-mask"></div>
<div class="scan-box">
<video id="videoElement" class="scan-video"></video>
<div class="scan-area"></div>
<div class="scan-controls">
<van-button type="primary" @click="stopScan">取消</van-button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {
onMounted,
ref,
toRefs,
defineAsyncComponent,
computed,
watch,
onUnmounted,
} from "vue";
import { Toast, Dialog } from "vant";
import { BrowserQRCodeReader, NotFoundException } from "@zxing/library";
import TaskApi from "@/modules/new_collect/api/taskApi";
import { useRouter, useRoute } from "vue-router";
const route = useRoute();
const router = useRouter();
const props = defineProps({
formData: Object,
item: Object,
isReadOnly: Boolean,
wholeFormData: Object,
subFormItem: Object,
subFormIdx: Number,
});
const { formData, item, isReadOnly, wholeFormData, subFormItem, subFormIdx } =
toRefs(props);
const isCompReadOnly = ref(false);
const isScanning = ref(false); // 掃碼狀態
let codeReader = null; // 掃碼器實例
onMounted(() => {
isCompReadOnly.value = isReadOnly.value || item.value.isReadOnly;
});
// 清理掃碼資源
onUnmounted(() => {
stopScan();
});
// 開始掃碼
const startScan = () => {
if (isCompReadOnly.value) return;
isScanning.value = true;
// 延遲執行,等待DOM渲染完成
setTimeout(() => {
codeReader = new BrowserQRCodeReader();
const videoInputDeviceId = null; // 可選:指定攝像頭ID
codeReader.decodeFromVideoDevice(
videoInputDeviceId,
"videoElement",
(result, err) => {
if (result) {
console.log("掃碼結果:", result.text);
formData.value[item.value.prop] = result.text;
Toast.success("掃碼成功");
stopScan();
getOptions();
}
if (err && !(err instanceof NotFoundException)) {
console.error(err);
Toast.fail("掃碼失敗,請重試");
}
}
);
}, 100);
};
// 停止掃碼
const stopScan = () => {
isScanning.value = false;
// 釋放攝像頭資源
if (codeReader) {
codeReader.reset();
codeReader = null;
}
};
const dataSource = ref();
const fieldOptions = ref([]);
const showDropdownPicker = ref(false);
const getOptions = async () => {
if (!item.value.sheetId || !item.value.field) {
return;
}
const { data } = await TaskApi.getSheetAndFieldByRules(
{ sheetName: item.value.sheetId },
[
{
field: item.value.field,
criteria: "=",
content: formData.value[item.value.prop],
},
]
);
if (data.code !== 200) {
Toast.fail(data.message);
return;
}
// showDropdownPicker.value = true;
dataSource.value = data.data.map((v) => {
return {
...v,
checked: false,
};
});
sessionStorage.setItem("formData", JSON.stringify(formData.value));
if (dataSource.value.length > 1) {
router.push("/ScanCodeBindSelect/list");
sessionStorage.setItem(
"currentDataSource",
JSON.stringify(dataSource.value)
);
} else if (dataSource.value.length == 1) {
dataSource.value[0].checked = true;
dataSource.value.forEach((v) => {
if (v.checked) {
Object.keys(item.value.fieldRelation).forEach((key) => {
Object.keys(v).forEach((g) => {
if (g == key) {
props.formData[item.value.fieldRelation[key]] = v[g];
}
});
});
}
});
const formData = sessionStorage.getItem("formData")
? JSON.parse(sessionStorage.getItem("formData"))
: {};
for (let k in formData) {
props.formData[k] = formData[k];
}
sessionStorage.removeItem("currentDataSource");
sessionStorage.removeItem("formData");
}
fieldOptions.value = data.data.map((item) => item.id);
};
watch(
() => sessionStorage.getItem("currentDataSource"),
(val) => {
if (val) {
if (sessionStorage.getItem("currentDataSource")) {
let dataSource = JSON.parse(val);
if (Array.isArray(dataSource))
dataSource &&
dataSource.forEach((v) => {
if (v.checked) {
Object.keys(item.value.fieldRelation).forEach((key) => {
Object.keys(v).forEach((g) => {
if (g == key) {
props.formData[item.value.fieldRelation[key]] = v[g];
}
});
});
}
});
const formData = sessionStorage.getItem("formData")
? JSON.parse(sessionStorage.getItem("formData"))
: {};
for (let k in formData) {
props.formData[k] = formData[k];
}
sessionStorage.removeItem("currentDataSource");
sessionStorage.removeItem("formData");
}
}
},
{
immediate: true,
deep: true,
}
);
const onDropdownConfirm = (val) => {
console.log(val);
showDropdownPicker.value = false;
};
</script>
<style scoped lang="less">
.custom-comp {
margin: 0px 20px;
width: 90%;
}
.component {
.custom-comp {
margin: 0;
width: 100%;
}
}
/* 掃碼界面樣式 */
.scan-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
}
.scan-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
}
.scan-box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 400px;
background-color: #fff;
border-radius: 8px;
overflow: hidden;
}
.scan-video {
width: 100%;
height: auto;
}
.scan-area {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
border: 2px solid #409eff;
box-shadow: 0 0 0 1000px rgba(0, 0, 0, 0.5);
background-color: transparent;
}
.scan-controls {
padding: 15px;
text-align: center;
}
.list {
padding-bottom: 60px;
}
.list-item {
margin: 16px 16px 0 16px;
border-radius: 8px;
width: auto;
}
.scan-icon {
font-size: 80px;
}
</style>
tips:必須是localhost或https才能使用掃碼功能

浙公網安備 33010602011771號