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.
542 lines
13 KiB
542 lines
13 KiB
<template>
|
|
<div class="heavy-component">
|
|
<h2>Heavy Data Processing Component</h2>
|
|
|
|
<!-- Data processing controls -->
|
|
<div class="controls">
|
|
<button @click="processData" :disabled="isProcessing">
|
|
{{ isProcessing ? 'Processing...' : 'Process Data' }}
|
|
</button>
|
|
<button @click="clearResults" :disabled="isProcessing">
|
|
Clear Results
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Processing status -->
|
|
<div v-if="isProcessing" class="processing-status">
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" :style="{ width: progress + '%' }"></div>
|
|
</div>
|
|
<p>Processing {{ processedCount }} of {{ totalItems }} items...</p>
|
|
</div>
|
|
|
|
<!-- Results display -->
|
|
<div v-if="processedData.length > 0" class="results">
|
|
<h3>Processed Results ({{ processedData.length }} items)</h3>
|
|
|
|
<!-- Filter controls -->
|
|
<div class="filters">
|
|
<input
|
|
v-model="searchTerm"
|
|
placeholder="Search items..."
|
|
class="search-input"
|
|
/>
|
|
<select v-model="sortBy" class="sort-select">
|
|
<option value="name">Sort by Name</option>
|
|
<option value="id">Sort by ID</option>
|
|
<option value="processed">Sort by Processed Date</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Results list -->
|
|
<div class="results-list">
|
|
<div
|
|
v-for="item in filteredAndSortedData"
|
|
:key="item.id"
|
|
class="result-item"
|
|
>
|
|
<div class="item-header">
|
|
<span class="item-name">{{ item.name }}</span>
|
|
<span class="item-id">#{{ item.id }}</span>
|
|
</div>
|
|
<div class="item-details">
|
|
<span class="processed-date">
|
|
Processed: {{ formatDate(item.processedAt) }}
|
|
</span>
|
|
<span class="processing-time">
|
|
Time: {{ item.processingTime }}ms
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div v-if="totalPages > 1" class="pagination">
|
|
<button
|
|
@click="previousPage"
|
|
:disabled="currentPage === 1"
|
|
class="page-btn"
|
|
>
|
|
Previous
|
|
</button>
|
|
<span class="page-info">
|
|
Page {{ currentPage }} of {{ totalPages }}
|
|
</span>
|
|
<button
|
|
@click="nextPage"
|
|
:disabled="currentPage === totalPages"
|
|
class="page-btn"
|
|
>
|
|
Next
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Performance metrics -->
|
|
<div v-if="performanceMetrics" class="performance-metrics">
|
|
<h4>Performance Metrics</h4>
|
|
<div class="metrics-grid">
|
|
<div class="metric">
|
|
<span class="metric-label">Total Processing Time:</span>
|
|
<span class="metric-value">{{ performanceMetrics.totalTime }}ms</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Average per Item:</span>
|
|
<span class="metric-value">{{ performanceMetrics.averageTime }}ms</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Memory Usage:</span>
|
|
<span class="metric-value">{{ performanceMetrics.memoryUsage }}MB</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Vue, Prop, Emit } from 'vue-facing-decorator';
|
|
|
|
interface ProcessedItem {
|
|
id: number;
|
|
name: string;
|
|
processedAt: Date;
|
|
processingTime: number;
|
|
result: any;
|
|
}
|
|
|
|
interface PerformanceMetrics {
|
|
totalTime: number;
|
|
averageTime: number;
|
|
memoryUsage: number;
|
|
}
|
|
|
|
/**
|
|
* Heavy Component for Data Processing
|
|
*
|
|
* Demonstrates a component that performs intensive data processing
|
|
* and would benefit from lazy loading to avoid blocking the main thread.
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
*/
|
|
@Component({
|
|
name: 'HeavyComponent'
|
|
})
|
|
export default class HeavyComponent extends Vue {
|
|
@Prop({ required: true }) readonly data!: {
|
|
items: Array<{ id: number; name: string }>;
|
|
filters: Record<string, any>;
|
|
sortBy: string;
|
|
};
|
|
|
|
// Component state
|
|
isProcessing = false;
|
|
processedData: ProcessedItem[] = [];
|
|
progress = 0;
|
|
processedCount = 0;
|
|
totalItems = 0;
|
|
|
|
// UI state
|
|
searchTerm = '';
|
|
sortBy = 'name';
|
|
currentPage = 1;
|
|
itemsPerPage = 50;
|
|
|
|
// Performance tracking
|
|
performanceMetrics: PerformanceMetrics | null = null;
|
|
startTime = 0;
|
|
|
|
// Computed properties
|
|
get filteredAndSortedData(): ProcessedItem[] {
|
|
let filtered = this.processedData;
|
|
|
|
// Apply search filter
|
|
if (this.searchTerm) {
|
|
filtered = filtered.filter(item =>
|
|
item.name.toLowerCase().includes(this.searchTerm.toLowerCase())
|
|
);
|
|
}
|
|
|
|
// Apply sorting
|
|
filtered.sort((a, b) => {
|
|
switch (this.sortBy) {
|
|
case 'name':
|
|
return a.name.localeCompare(b.name);
|
|
case 'id':
|
|
return a.id - b.id;
|
|
case 'processed':
|
|
return b.processedAt.getTime() - a.processedAt.getTime();
|
|
default:
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
return filtered;
|
|
}
|
|
|
|
get paginatedData(): ProcessedItem[] {
|
|
const start = (this.currentPage - 1) * this.itemsPerPage;
|
|
const end = start + this.itemsPerPage;
|
|
return this.filteredAndSortedData.slice(start, end);
|
|
}
|
|
|
|
get totalPages(): number {
|
|
return Math.ceil(this.filteredAndSortedData.length / this.itemsPerPage);
|
|
}
|
|
|
|
// Lifecycle hooks
|
|
mounted(): void {
|
|
console.log('[HeavyComponent] Component mounted with', this.data.items.length, 'items');
|
|
this.totalItems = this.data.items.length;
|
|
}
|
|
|
|
// Methods
|
|
async processData(): Promise<void> {
|
|
if (this.isProcessing) return;
|
|
|
|
this.isProcessing = true;
|
|
this.progress = 0;
|
|
this.processedCount = 0;
|
|
this.processedData = [];
|
|
this.startTime = performance.now();
|
|
|
|
console.log('[HeavyComponent] Starting data processing...');
|
|
|
|
try {
|
|
// Process items in batches to avoid blocking the UI
|
|
const batchSize = 10;
|
|
const items = this.data.items;
|
|
|
|
for (let i = 0; i < items.length; i += batchSize) {
|
|
const batch = items.slice(i, i + batchSize);
|
|
|
|
// Process batch
|
|
await this.processBatch(batch);
|
|
|
|
// Update progress
|
|
this.processedCount = Math.min(i + batchSize, items.length);
|
|
this.progress = (this.processedCount / items.length) * 100;
|
|
|
|
// Allow UI to update
|
|
await this.$nextTick();
|
|
|
|
// Small delay to prevent overwhelming the UI
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
}
|
|
|
|
// Calculate performance metrics
|
|
this.calculatePerformanceMetrics();
|
|
|
|
// Emit completion event
|
|
this.$emit('data-processed', {
|
|
totalItems: this.processedData.length,
|
|
processingTime: performance.now() - this.startTime,
|
|
metrics: this.performanceMetrics
|
|
});
|
|
|
|
console.log('[HeavyComponent] Data processing completed');
|
|
|
|
} catch (error) {
|
|
console.error('[HeavyComponent] Processing error:', error);
|
|
this.$emit('processing-error', error);
|
|
} finally {
|
|
this.isProcessing = false;
|
|
}
|
|
}
|
|
|
|
private async processBatch(batch: Array<{ id: number; name: string }>): Promise<void> {
|
|
const processedBatch = await Promise.all(
|
|
batch.map(async (item) => {
|
|
const itemStartTime = performance.now();
|
|
|
|
// Simulate heavy processing
|
|
await this.simulateHeavyProcessing(item);
|
|
|
|
const processingTime = performance.now() - itemStartTime;
|
|
|
|
return {
|
|
id: item.id,
|
|
name: item.name,
|
|
processedAt: new Date(),
|
|
processingTime: Math.round(processingTime),
|
|
result: this.generateResult(item)
|
|
};
|
|
})
|
|
);
|
|
|
|
this.processedData.push(...processedBatch);
|
|
}
|
|
|
|
private async simulateHeavyProcessing(item: { id: number; name: string }): Promise<void> {
|
|
// Simulate CPU-intensive work
|
|
const complexity = item.name.length * item.id;
|
|
const iterations = Math.min(complexity, 1000); // Cap at 1000 iterations
|
|
|
|
for (let i = 0; i < iterations; i++) {
|
|
// Simulate work
|
|
Math.sqrt(i) * Math.random();
|
|
}
|
|
|
|
// Simulate async work
|
|
await new Promise(resolve => setTimeout(resolve, Math.random() * 10));
|
|
}
|
|
|
|
private generateResult(item: { id: number; name: string }): any {
|
|
return {
|
|
hash: this.generateHash(item.name + item.id),
|
|
category: this.categorizeItem(item),
|
|
score: Math.random() * 100,
|
|
tags: this.generateTags(item)
|
|
};
|
|
}
|
|
|
|
private generateHash(input: string): string {
|
|
let hash = 0;
|
|
for (let i = 0; i < input.length; i++) {
|
|
const char = input.charCodeAt(i);
|
|
hash = ((hash << 5) - hash) + char;
|
|
hash = hash & hash; // Convert to 32-bit integer
|
|
}
|
|
return hash.toString(16);
|
|
}
|
|
|
|
private categorizeItem(item: { id: number; name: string }): string {
|
|
const categories = ['A', 'B', 'C', 'D', 'E'];
|
|
return categories[item.id % categories.length];
|
|
}
|
|
|
|
private generateTags(item: { id: number; name: string }): string[] {
|
|
const tags = ['important', 'urgent', 'review', 'archive', 'featured'];
|
|
return tags.filter((_, index) => (item.id + index) % 3 === 0);
|
|
}
|
|
|
|
private calculatePerformanceMetrics(): void {
|
|
const totalTime = performance.now() - this.startTime;
|
|
const averageTime = totalTime / this.processedData.length;
|
|
|
|
// Simulate memory usage calculation
|
|
const memoryUsage = this.processedData.length * 0.1; // 0.1MB per item
|
|
|
|
this.performanceMetrics = {
|
|
totalTime: Math.round(totalTime),
|
|
averageTime: Math.round(averageTime),
|
|
memoryUsage: Math.round(memoryUsage * 100) / 100
|
|
};
|
|
}
|
|
|
|
clearResults(): void {
|
|
this.processedData = [];
|
|
this.performanceMetrics = null;
|
|
this.searchTerm = '';
|
|
this.currentPage = 1;
|
|
console.log('[HeavyComponent] Results cleared');
|
|
}
|
|
|
|
previousPage(): void {
|
|
if (this.currentPage > 1) {
|
|
this.currentPage--;
|
|
}
|
|
}
|
|
|
|
nextPage(): void {
|
|
if (this.currentPage < this.totalPages) {
|
|
this.currentPage++;
|
|
}
|
|
}
|
|
|
|
formatDate(date: Date): string {
|
|
return date.toLocaleString();
|
|
}
|
|
|
|
// Event emitters
|
|
@Emit('data-processed')
|
|
emitDataProcessed(data: any): any {
|
|
return data;
|
|
}
|
|
|
|
@Emit('processing-error')
|
|
emitProcessingError(error: Error): Error {
|
|
return error;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.heavy-component {
|
|
padding: 20px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 8px;
|
|
background: #f9f9f9;
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.controls button {
|
|
padding: 8px 16px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: #fff;
|
|
cursor: pointer;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.controls button:hover:not(:disabled) {
|
|
background: #e9ecef;
|
|
}
|
|
|
|
.controls button:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.processing-status {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.progress-bar {
|
|
width: 100%;
|
|
height: 20px;
|
|
background: #e9ecef;
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #007bff, #0056b3);
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
.results {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.filters {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.search-input,
|
|
.sort-select {
|
|
padding: 8px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.search-input {
|
|
flex: 1;
|
|
}
|
|
|
|
.results-list {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background: #fff;
|
|
}
|
|
|
|
.result-item {
|
|
padding: 12px;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.result-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.item-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.item-name {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.item-id {
|
|
color: #666;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.item-details {
|
|
display: flex;
|
|
gap: 20px;
|
|
font-size: 0.85em;
|
|
color: #666;
|
|
}
|
|
|
|
.pagination {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 15px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.page-btn {
|
|
padding: 6px 12px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: #fff;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.page-btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.page-info {
|
|
font-size: 0.9em;
|
|
color: #666;
|
|
}
|
|
|
|
.performance-metrics {
|
|
margin-top: 20px;
|
|
padding: 15px;
|
|
background: #e8f4fd;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.metrics-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 15px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.metric {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 8px;
|
|
background: #fff;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.metric-label {
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
|
|
.metric-value {
|
|
color: #007bff;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|