FPSMS-frontend
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

QrCodeScanner.tsx 7.5 KiB

11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
11 个月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import {
  2. Autocomplete,
  3. Box,
  4. Button,
  5. Card,
  6. CardContent,
  7. Grid,
  8. Modal,
  9. ModalProps,
  10. Stack,
  11. SxProps,
  12. TextField,
  13. Typography,
  14. } from "@mui/material";
  15. import {
  16. CameraDevice,
  17. Html5Qrcode,
  18. Html5QrcodeCameraScanConfig,
  19. Html5QrcodeFullConfig,
  20. Html5QrcodeResult,
  21. Html5QrcodeScanner,
  22. Html5QrcodeScannerState,
  23. QrcodeErrorCallback,
  24. QrcodeSuccessCallback,
  25. } from "html5-qrcode";
  26. import { Html5QrcodeError } from "html5-qrcode/esm/core";
  27. import { Html5QrcodeScannerConfig } from "html5-qrcode/esm/html5-qrcode-scanner";
  28. import React, {
  29. RefObject,
  30. useCallback,
  31. useEffect,
  32. useMemo,
  33. useRef,
  34. useState,
  35. } from "react";
  36. import { useTranslation } from "react-i18next";
  37. import QrCodeScannerIcon from "@mui/icons-material/QrCodeScanner";
  38. import StopCircleOutlinedIcon from "@mui/icons-material/StopCircleOutlined";
  39. import CloseOutlinedIcon from "@mui/icons-material/CloseOutlined";
  40. import { QrCodeInfo } from "@/app/api/qrcode";
  41. const scannerSx: React.CSSProperties = {
  42. position: "absolute",
  43. // top: "50%",
  44. // left: "50%",
  45. // transform: "translate(-50%, -50%)",
  46. width: "90%",
  47. maxHeight: "10%",
  48. maxWidth: 1400,
  49. };
  50. type QrCodeScannerProps = {
  51. cameras: CameraDevice[];
  52. title?: string;
  53. contents?: string[];
  54. onScanSuccess: (qrCodeInfo: QrCodeInfo) => void;
  55. onScanError?: (error: string) => void;
  56. isOpen: boolean;
  57. onClose: () => void;
  58. };
  59. const QrCodeScanner: React.FC<QrCodeScannerProps> = ({
  60. title,
  61. contents,
  62. onScanSuccess,
  63. onScanError,
  64. isOpen,
  65. onClose,
  66. }) => {
  67. const { t } = useTranslation();
  68. const [isScanned, setIsScanned] = useState<boolean>(false);
  69. const [scanner, setScanner] = useState<Html5Qrcode | null>(null);
  70. const [cameraList, setCameraList] = useState<CameraDevice[]>([]);
  71. const [selectedCameraId, setSelectedCameraId] = useState<string | null>(null);
  72. const stringList = [
  73. "ABC: abc",
  74. "123:123",
  75. "ABC: abc",
  76. "123:123",
  77. "ABC: abc",
  78. "123:123",
  79. ];
  80. const scannerConfig: Html5QrcodeFullConfig = {
  81. verbose: false,
  82. };
  83. const cameraConfig: Html5QrcodeCameraScanConfig = {
  84. fps: 10,
  85. qrbox: { width: 250, height: 250 },
  86. // aspectRatio: cardRef.current ? (cardRef.current.offsetWidth / cardRef.current.offsetHeight) : 1.78,
  87. aspectRatio: (window.innerWidth / window.innerHeight) * 1.5, // can be better
  88. };
  89. // MediaTrackConstraintSet
  90. const mediaTrackConstraintSet = {
  91. facingMode: "environment",
  92. };
  93. const handleScanStart = useCallback(() => {
  94. if (scanner && selectedCameraId) {
  95. if (scanner.getState() === Html5QrcodeScannerState.SCANNING) {
  96. console.log("first");
  97. scanner.stop();
  98. }
  99. scanner.start(
  100. selectedCameraId,
  101. cameraConfig,
  102. handleScanSuccess,
  103. handleScanError,
  104. );
  105. }
  106. }, [selectedCameraId, scanner]);
  107. const handleCameraList = useCallback(async () => {
  108. const cameras = await Html5Qrcode.getCameras();
  109. setCameraList(cameras);
  110. if (cameras.length > 0) {
  111. handleCameraChange(cameras[cameras.length - 1].id);
  112. }
  113. }, []);
  114. const handleCameraChange = useCallback((id: string) => {
  115. setSelectedCameraId(id);
  116. }, []);
  117. const switchScanStatus = useCallback(() => {
  118. if (scanner) {
  119. console.log(isScanned);
  120. switch (isScanned) {
  121. case true:
  122. setIsScanned(false);
  123. scanner.resume();
  124. break;
  125. case false:
  126. setIsScanned(true);
  127. scanner.pause(true);
  128. break;
  129. }
  130. }
  131. }, [scanner, isScanned]);
  132. const handleScanSuccess = useCallback<QrcodeSuccessCallback>(
  133. (decodedText, result) => {
  134. if (scanner) {
  135. console.log(`Decoded text: ${decodedText}`);
  136. const parseData: QrCodeInfo = JSON.parse(decodedText);
  137. console.log(parseData);
  138. // Handle the decoded text as needed
  139. switchScanStatus();
  140. onScanSuccess(parseData);
  141. }
  142. },
  143. [scanner, onScanSuccess],
  144. );
  145. const handleScanError = useCallback<QrcodeErrorCallback>(
  146. (errorMessage, error) => {
  147. // console.log(`Error: ${errorMessage}`);
  148. if (onScanError) {
  149. onScanError(errorMessage);
  150. }
  151. },
  152. [scanner, onScanError],
  153. );
  154. const handleScanCloseButton = useCallback(async () => {
  155. if (scanner) {
  156. console.log("Cleaning up scanner...");
  157. await scanner.stop();
  158. scanner.clear();
  159. onClose();
  160. }
  161. }, [scanner]);
  162. // close modal without using Cancel Button
  163. const handleScanClose = useCallback(async () => {
  164. if (scanner && !isOpen) {
  165. handleScanCloseButton();
  166. }
  167. }, [scanner, isOpen, handleScanCloseButton]);
  168. // -------------------------------------------------------//
  169. useEffect(() => {
  170. setScanner(new Html5Qrcode("qr-reader", scannerConfig));
  171. handleCameraList();
  172. }, []);
  173. useEffect(() => {
  174. handleScanStart();
  175. }, [scanner, selectedCameraId]);
  176. useEffect(() => {
  177. handleScanClose();
  178. }, [isOpen]);
  179. return (
  180. <>
  181. <Stack spacing={2}>
  182. {title && (
  183. <Typography
  184. variant="overline"
  185. display="block"
  186. marginBlockEnd={1}
  187. paddingLeft={2}
  188. >
  189. {"Title"}
  190. </Typography>
  191. )}
  192. <Grid
  193. container
  194. alignItems="center"
  195. justifyContent="center"
  196. rowSpacing={2}
  197. columns={{ xs: 6, sm: 12 }}
  198. >
  199. <Grid item xs={12}>
  200. <div
  201. style={{
  202. textAlign: "center",
  203. margin: "auto",
  204. justifyContent: "center",
  205. }}
  206. id="qr-reader"
  207. hidden={isScanned}
  208. />
  209. </Grid>
  210. {/* {cameraList.length > 0 && <Grid item xs={6} >
  211. <Autocomplete
  212. disableClearable
  213. noOptionsText={t("No Options")}
  214. options={cameraList}
  215. value={cameraList.find((camera) => camera.id === selectedCameraId)}
  216. onChange={((event, value) => {
  217. setSelectedCameraId(value.id)
  218. })}
  219. renderInput={(params) => (
  220. <TextField
  221. {...params}
  222. variant="outlined"
  223. label={t("Selected Camera")}
  224. />
  225. )}
  226. />
  227. </Grid>} */}
  228. {contents &&
  229. contents.map((string) => {
  230. return (
  231. <Grid item xs={8}>
  232. {string}
  233. </Grid>
  234. );
  235. })}
  236. </Grid>
  237. <Stack
  238. direction="row"
  239. justifyContent={"flex-end"}
  240. spacing={2}
  241. sx={{ margin: 2 }}
  242. >
  243. <Button
  244. size="small"
  245. onClick={switchScanStatus}
  246. variant="contained"
  247. // sx={{ margin: 2 }}
  248. startIcon={
  249. isScanned ? <QrCodeScannerIcon /> : <StopCircleOutlinedIcon />
  250. }
  251. >
  252. {isScanned ? t("Start Scanning") : t("Stop Scanning")}
  253. </Button>
  254. <Button
  255. size="small"
  256. onClick={handleScanCloseButton}
  257. variant="outlined"
  258. // color="error"
  259. // sx={{ margin: 2 }}
  260. startIcon={<CloseOutlinedIcon />}
  261. >
  262. {t("Cancel")}
  263. </Button>
  264. </Stack>
  265. </Stack>
  266. </>
  267. );
  268. };
  269. export default QrCodeScanner;