You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
568 lines
12 KiB
568 lines
12 KiB
---
|
|
description:
|
|
globs:
|
|
alwaysApply: true
|
|
---
|
|
# QR Code Implementation Guide
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
src/
|
|
├── components/
|
|
│ └── QRScanner/
|
|
│ ├── types.ts
|
|
│ ├── factory.ts
|
|
│ ├── CapacitorScanner.ts
|
|
│ ├── WebDialogScanner.ts
|
|
│ └── QRScannerDialog.vue
|
|
├── services/
|
|
│ └── QRScanner/
|
|
│ ├── types.ts
|
|
│ ├── QRScannerFactory.ts
|
|
│ ├── CapacitorQRScanner.ts
|
|
│ └── WebDialogQRScanner.ts
|
|
```
|
|
|
|
## Core Interfaces
|
|
|
|
```typescript
|
|
// types.ts
|
|
export interface ScanListener {
|
|
onScan: (result: string) => void;
|
|
onError?: (error: Error) => void;
|
|
}
|
|
|
|
export interface QRScannerService {
|
|
checkPermissions(): Promise<boolean>;
|
|
requestPermissions(): Promise<boolean>;
|
|
isSupported(): Promise<boolean>;
|
|
startScan(): Promise<void>;
|
|
stopScan(): Promise<void>;
|
|
addListener(listener: ScanListener): void;
|
|
cleanup(): Promise<void>;
|
|
}
|
|
```
|
|
|
|
## Configuration Files
|
|
|
|
### Vite Configuration
|
|
```typescript
|
|
// vite.config.ts
|
|
export default defineConfig({
|
|
define: {
|
|
__USE_QR_READER__: JSON.stringify(!isMobile),
|
|
__IS_MOBILE__: JSON.stringify(isMobile),
|
|
},
|
|
build: {
|
|
rollupOptions: {
|
|
external: isMobile ? ['vue-qrcode-reader'] : [],
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### Capacitor Configuration
|
|
```typescript
|
|
// capacitor.config.ts
|
|
const config: CapacitorConfig = {
|
|
plugins: {
|
|
MLKitBarcodeScanner: {
|
|
formats: ['QR_CODE'],
|
|
detectorSize: 1.0,
|
|
lensFacing: 'back',
|
|
googleBarcodeScannerModuleInstallState: true
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
## Implementation Steps
|
|
|
|
1. **Install Dependencies**
|
|
```bash
|
|
npm install @capacitor-mlkit/barcode-scanning vue-qrcode-reader
|
|
```
|
|
|
|
2. **Create Core Types**
|
|
Create the interface files as shown above.
|
|
|
|
3. **Implement Factory**
|
|
```typescript
|
|
// QRScannerFactory.ts
|
|
export class QRScannerFactory {
|
|
private static instance: QRScannerService | null = null;
|
|
|
|
static getInstance(): QRScannerService {
|
|
if (!this.instance) {
|
|
if (__IS_MOBILE__ || Capacitor.isNativePlatform()) {
|
|
this.instance = new CapacitorQRScanner();
|
|
} else if (__USE_QR_READER__) {
|
|
this.instance = new WebDialogQRScanner();
|
|
} else {
|
|
throw new Error('No QR scanner implementation available');
|
|
}
|
|
}
|
|
return this.instance;
|
|
}
|
|
|
|
static async cleanup() {
|
|
if (this.instance) {
|
|
await this.instance.cleanup();
|
|
this.instance = null;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
4. **Implement Mobile Scanner**
|
|
```typescript
|
|
// CapacitorQRScanner.ts
|
|
export class CapacitorQRScanner implements QRScannerService {
|
|
private scanListener: ScanListener | null = null;
|
|
private isScanning = false;
|
|
private listenerHandles: Array<() => Promise<void>> = [];
|
|
|
|
async checkPermissions() {
|
|
try {
|
|
const { camera } = await BarcodeScanner.checkPermissions();
|
|
return camera === 'granted';
|
|
} catch (error) {
|
|
logger.error('Error checking camera permissions:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Implement other interface methods...
|
|
}
|
|
```
|
|
|
|
5. **Implement Web Scanner**
|
|
```typescript
|
|
// WebDialogQRScanner.ts
|
|
export class WebDialogQRScanner implements QRScannerService {
|
|
private dialogInstance: App | null = null;
|
|
private dialogComponent: InstanceType<typeof QRScannerDialog> | null = null;
|
|
private scanListener: ScanListener | null = null;
|
|
async checkPermissions(): Promise<boolean> {
|
|
try {
|
|
const permissions = await navigator.permissions.query({
|
|
name: 'camera' as PermissionName
|
|
});
|
|
return permissions.state === 'granted';
|
|
} catch (error) {
|
|
logger.error('Error checking camera permissions:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Implement other interface methods...
|
|
}
|
|
```
|
|
|
|
6. **Create Dialog Component**
|
|
```vue
|
|
<!-- QRScannerDialog.vue -->
|
|
<template>
|
|
<div v-if="visible" class="dialog-overlay z-[60]">
|
|
<div class="dialog relative">
|
|
<!-- Dialog content -->
|
|
<div v-if="useQRReader">
|
|
<qrcode-stream
|
|
class="w-full max-w-lg mx-auto"
|
|
@detect="onScanDetect"
|
|
@error="onScanError"
|
|
/>
|
|
</div>
|
|
<div v-else>
|
|
<!-- Mobile camera button -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
@Component({
|
|
components: { QrcodeStream }
|
|
})
|
|
export default class QRScannerDialog extends Vue {
|
|
// Implementation...
|
|
}
|
|
</script>
|
|
```
|
|
|
|
## Usage Example
|
|
|
|
```typescript
|
|
// In your component
|
|
async function scanQRCode() {
|
|
const scanner = QRScannerFactory.getInstance();
|
|
|
|
if (!(await scanner.checkPermissions())) {
|
|
const granted = await scanner.requestPermissions();
|
|
if (!granted) {
|
|
throw new Error('Camera permission denied');
|
|
}
|
|
}
|
|
|
|
scanner.addListener({
|
|
onScan: (result) => {
|
|
console.log('Scanned:', result);
|
|
},
|
|
onError: (error) => {
|
|
console.error('Scan error:', error);
|
|
}
|
|
});
|
|
|
|
await scanner.startScan();
|
|
}
|
|
|
|
// Cleanup when done
|
|
onUnmounted(() => {
|
|
QRScannerFactory.cleanup();
|
|
});
|
|
```
|
|
|
|
## Platform-Specific Notes
|
|
|
|
### Mobile (Capacitor)
|
|
- Uses MLKit for optimal performance
|
|
- Handles native permissions
|
|
- Supports both iOS and Android
|
|
- Uses back camera by default
|
|
- Handles device rotation
|
|
|
|
### Web
|
|
- Uses MediaDevices API
|
|
- Requires HTTPS for camera access
|
|
- Handles browser compatibility
|
|
- Manages memory and resources
|
|
- Provides fallback UI
|
|
|
|
## Testing
|
|
|
|
1. **Unit Tests**
|
|
- Test factory pattern
|
|
- Test platform detection
|
|
- Test error handling
|
|
- Test cleanup procedures
|
|
|
|
2. **Integration Tests**
|
|
- Test permission flows
|
|
- Test camera access
|
|
- Test QR code detection
|
|
- Test cross-platform behavior
|
|
|
|
3. **E2E Tests**
|
|
- Test full scanning workflow
|
|
- Test UI feedback
|
|
- Test error scenarios
|
|
- Test platform differences
|
|
|
|
## Common Issues and Solutions
|
|
|
|
1. **Permission Handling**
|
|
- Always check permissions first
|
|
- Provide clear user feedback
|
|
- Handle denial gracefully
|
|
- Implement retry logic
|
|
|
|
2. **Resource Management**
|
|
- Clean up after scanning
|
|
- Handle component unmounting
|
|
- Release camera resources
|
|
- Clear event listeners
|
|
|
|
3. **Error Handling**
|
|
- Log errors appropriately
|
|
- Provide user feedback
|
|
- Implement fallbacks
|
|
- Handle edge cases
|
|
|
|
4. **Performance**
|
|
- Optimize camera preview
|
|
- Handle memory usage
|
|
- Manage battery impact
|
|
- Consider device capabilities
|
|
|
|
# QR Code Implementation Guide
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
src/
|
|
├── components/
|
|
│ └── QRScanner/
|
|
│ ├── types.ts
|
|
│ ├── factory.ts
|
|
│ ├── CapacitorScanner.ts
|
|
│ ├── WebDialogScanner.ts
|
|
│ └── QRScannerDialog.vue
|
|
├── services/
|
|
│ └── QRScanner/
|
|
│ ├── types.ts
|
|
│ ├── QRScannerFactory.ts
|
|
│ ├── CapacitorQRScanner.ts
|
|
│ └── WebDialogQRScanner.ts
|
|
```
|
|
|
|
## Core Interfaces
|
|
|
|
```typescript
|
|
// types.ts
|
|
export interface ScanListener {
|
|
onScan: (result: string) => void;
|
|
onError?: (error: Error) => void;
|
|
}
|
|
|
|
export interface QRScannerService {
|
|
checkPermissions(): Promise<boolean>;
|
|
requestPermissions(): Promise<boolean>;
|
|
isSupported(): Promise<boolean>;
|
|
startScan(): Promise<void>;
|
|
stopScan(): Promise<void>;
|
|
addListener(listener: ScanListener): void;
|
|
cleanup(): Promise<void>;
|
|
}
|
|
```
|
|
|
|
## Configuration Files
|
|
|
|
### Vite Configuration
|
|
```typescript
|
|
// vite.config.ts
|
|
export default defineConfig({
|
|
define: {
|
|
__USE_QR_READER__: JSON.stringify(!isMobile),
|
|
__IS_MOBILE__: JSON.stringify(isMobile),
|
|
},
|
|
build: {
|
|
rollupOptions: {
|
|
external: isMobile ? ['vue-qrcode-reader'] : [],
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### Capacitor Configuration
|
|
```typescript
|
|
// capacitor.config.ts
|
|
const config: CapacitorConfig = {
|
|
plugins: {
|
|
MLKitBarcodeScanner: {
|
|
formats: ['QR_CODE'],
|
|
detectorSize: 1.0,
|
|
lensFacing: 'back',
|
|
googleBarcodeScannerModuleInstallState: true
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
## Implementation Steps
|
|
|
|
1. **Install Dependencies**
|
|
```bash
|
|
npm install @capacitor-mlkit/barcode-scanning vue-qrcode-reader
|
|
```
|
|
|
|
2. **Create Core Types**
|
|
Create the interface files as shown above.
|
|
|
|
3. **Implement Factory**
|
|
```typescript
|
|
// QRScannerFactory.ts
|
|
export class QRScannerFactory {
|
|
private static instance: QRScannerService | null = null;
|
|
|
|
static getInstance(): QRScannerService {
|
|
if (!this.instance) {
|
|
if (__IS_MOBILE__ || Capacitor.isNativePlatform()) {
|
|
this.instance = new CapacitorQRScanner();
|
|
} else if (__USE_QR_READER__) {
|
|
this.instance = new WebDialogQRScanner();
|
|
} else {
|
|
throw new Error('No QR scanner implementation available');
|
|
}
|
|
}
|
|
return this.instance;
|
|
}
|
|
|
|
static async cleanup() {
|
|
if (this.instance) {
|
|
await this.instance.cleanup();
|
|
this.instance = null;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
4. **Implement Mobile Scanner**
|
|
```typescript
|
|
// CapacitorQRScanner.ts
|
|
export class CapacitorQRScanner implements QRScannerService {
|
|
private scanListener: ScanListener | null = null;
|
|
private isScanning = false;
|
|
private listenerHandles: Array<() => Promise<void>> = [];
|
|
|
|
async checkPermissions() {
|
|
try {
|
|
const { camera } = await BarcodeScanner.checkPermissions();
|
|
return camera === 'granted';
|
|
} catch (error) {
|
|
logger.error('Error checking camera permissions:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Implement other interface methods...
|
|
}
|
|
```
|
|
|
|
5. **Implement Web Scanner**
|
|
```typescript
|
|
// WebDialogQRScanner.ts
|
|
export class WebDialogQRScanner implements QRScannerService {
|
|
private dialogInstance: App | null = null;
|
|
private dialogComponent: InstanceType<typeof QRScannerDialog> | null = null;
|
|
private scanListener: ScanListener | null = null;
|
|
async checkPermissions(): Promise<boolean> {
|
|
try {
|
|
const permissions = await navigator.permissions.query({
|
|
name: 'camera' as PermissionName
|
|
});
|
|
return permissions.state === 'granted';
|
|
} catch (error) {
|
|
logger.error('Error checking camera permissions:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Implement other interface methods...
|
|
}
|
|
```
|
|
|
|
6. **Create Dialog Component**
|
|
```vue
|
|
<!-- QRScannerDialog.vue -->
|
|
<template>
|
|
<div v-if="visible" class="dialog-overlay z-[60]">
|
|
<div class="dialog relative">
|
|
<!-- Dialog content -->
|
|
<div v-if="useQRReader">
|
|
<qrcode-stream
|
|
class="w-full max-w-lg mx-auto"
|
|
@detect="onScanDetect"
|
|
@error="onScanError"
|
|
/>
|
|
</div>
|
|
<div v-else>
|
|
<!-- Mobile camera button -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
@Component({
|
|
components: { QrcodeStream }
|
|
})
|
|
export default class QRScannerDialog extends Vue {
|
|
// Implementation...
|
|
}
|
|
</script>
|
|
```
|
|
|
|
## Usage Example
|
|
|
|
```typescript
|
|
// In your component
|
|
async function scanQRCode() {
|
|
const scanner = QRScannerFactory.getInstance();
|
|
|
|
if (!(await scanner.checkPermissions())) {
|
|
const granted = await scanner.requestPermissions();
|
|
if (!granted) {
|
|
throw new Error('Camera permission denied');
|
|
}
|
|
}
|
|
|
|
scanner.addListener({
|
|
onScan: (result) => {
|
|
console.log('Scanned:', result);
|
|
},
|
|
onError: (error) => {
|
|
console.error('Scan error:', error);
|
|
}
|
|
});
|
|
|
|
await scanner.startScan();
|
|
}
|
|
|
|
// Cleanup when done
|
|
onUnmounted(() => {
|
|
QRScannerFactory.cleanup();
|
|
});
|
|
```
|
|
|
|
## Platform-Specific Notes
|
|
|
|
### Mobile (Capacitor)
|
|
- Uses MLKit for optimal performance
|
|
- Handles native permissions
|
|
- Supports both iOS and Android
|
|
- Uses back camera by default
|
|
- Handles device rotation
|
|
|
|
### Web
|
|
- Uses MediaDevices API
|
|
- Requires HTTPS for camera access
|
|
- Handles browser compatibility
|
|
- Manages memory and resources
|
|
- Provides fallback UI
|
|
|
|
## Testing
|
|
|
|
1. **Unit Tests**
|
|
- Test factory pattern
|
|
- Test platform detection
|
|
- Test error handling
|
|
- Test cleanup procedures
|
|
|
|
2. **Integration Tests**
|
|
- Test permission flows
|
|
- Test camera access
|
|
- Test QR code detection
|
|
- Test cross-platform behavior
|
|
|
|
3. **E2E Tests**
|
|
- Test full scanning workflow
|
|
- Test UI feedback
|
|
- Test error scenarios
|
|
- Test platform differences
|
|
|
|
## Common Issues and Solutions
|
|
|
|
1. **Permission Handling**
|
|
- Always check permissions first
|
|
- Provide clear user feedback
|
|
- Handle denial gracefully
|
|
- Implement retry logic
|
|
|
|
2. **Resource Management**
|
|
- Clean up after scanning
|
|
- Handle component unmounting
|
|
- Release camera resources
|
|
- Clear event listeners
|
|
|
|
3. **Error Handling**
|
|
- Log errors appropriately
|
|
- Provide user feedback
|
|
- Implement fallbacks
|
|
- Handle edge cases
|
|
|
|
4. **Performance**
|
|
- Optimize camera preview
|
|
- Handle memory usage
|
|
- Manage battery impact
|
|
- Consider device capabilities
|
|
|