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

---
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