@vc-shell/create-vc-app 1.1.99-alpha.2 → 1.1.99-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -5
- package/dist/cli/argv.d.ts.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/types.d.ts +5 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/commands/generate-blade.d.ts +5 -0
- package/dist/commands/generate-blade.d.ts.map +1 -1
- package/dist/index.js +940 -789
- package/dist/templates/base/_package.json +5 -5
- package/dist/templates/blades/grid/blade.vue +4 -15
- package/dist/utils/naming.d.ts +1 -0
- package/dist/utils/naming.d.ts.map +1 -1
- package/dist/utils/register-module.d.ts +4 -0
- package/dist/utils/register-module.d.ts.map +1 -1
- package/dist/utils/templates.d.ts +10 -0
- package/dist/utils/templates.d.ts.map +1 -0
- package/dist/workflows/create-app.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/templates/base/ai-guides/.cursorrules-vc-shell +0 -529
- package/dist/templates/base/ai-guides/README.md +0 -360
- package/dist/templates/base/ai-guides/guides/AI_GUIDE.md +0 -195
- package/dist/templates/base/ai-guides/guides/blade-patterns.md +0 -384
- package/dist/templates/base/ai-guides/guides/complete-workflow.md +0 -781
- package/dist/templates/base/ai-guides/guides/composables-reference.md +0 -338
- package/dist/templates/base/ai-guides/guides/troubleshooting.md +0 -529
- package/dist/templates/base/ai-guides/guides/ui-components-reference.md +0 -903
- package/dist/templates/base/ai-guides/prompts/adapt-existing-module.md +0 -1026
- package/dist/templates/base/ai-guides/prompts/advanced-scenarios.md +0 -852
- package/dist/templates/base/ai-guides/prompts/api-client-generation.md +0 -877
- package/dist/templates/base/ai-guides/prompts/cli-usage.md +0 -640
- package/dist/templates/base/ai-guides/prompts/quick-start-scenarios.md +0 -773
- package/dist/templates/base/ai-guides/prompts/simple-modifications.md +0 -987
|
@@ -1,852 +0,0 @@
|
|
|
1
|
-
# Advanced Scenarios
|
|
2
|
-
|
|
3
|
-
Complex customization patterns and advanced use cases for VC Shell applications.
|
|
4
|
-
|
|
5
|
-
## When to Use This
|
|
6
|
-
|
|
7
|
-
Use these prompts for:
|
|
8
|
-
- Complex forms with dynamic fields
|
|
9
|
-
- Master-detail relationships
|
|
10
|
-
- Multi-step wizards
|
|
11
|
-
- Advanced validation
|
|
12
|
-
- File uploads with processing
|
|
13
|
-
- Real-time updates
|
|
14
|
-
- Complex business logic
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Scenario 1: Dynamic Form Fields
|
|
19
|
-
|
|
20
|
-
**Use Case:** Form fields change based on other field values
|
|
21
|
-
|
|
22
|
-
**Prompt for AI:**
|
|
23
|
-
```
|
|
24
|
-
Create a product details blade where form fields change dynamically.
|
|
25
|
-
|
|
26
|
-
Requirements:
|
|
27
|
-
- Product type field (select: Physical, Digital, Service)
|
|
28
|
-
- When "Physical" selected, show:
|
|
29
|
-
- Weight (number)
|
|
30
|
-
- Dimensions (length x width x height)
|
|
31
|
-
- Shipping required (checkbox)
|
|
32
|
-
- When "Digital" selected, show:
|
|
33
|
-
- Download URL (text)
|
|
34
|
-
- File size (number)
|
|
35
|
-
- License key (text, auto-generated)
|
|
36
|
-
- When "Service" selected, show:
|
|
37
|
-
- Duration (number, hours)
|
|
38
|
-
- Service location (select: On-site, Remote, Hybrid)
|
|
39
|
-
- Required skills (multivalue)
|
|
40
|
-
|
|
41
|
-
All conditional fields should appear/disappear smoothly.
|
|
42
|
-
Use computed properties to control visibility.
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**Expected Implementation:**
|
|
46
|
-
```typescript
|
|
47
|
-
const productType = ref<'physical' | 'digital' | 'service'>('physical');
|
|
48
|
-
|
|
49
|
-
const showPhysicalFields = computed(() => productType.value === 'physical');
|
|
50
|
-
const showDigitalFields = computed(() => productType.value === 'digital');
|
|
51
|
-
const showServiceFields = computed(() => productType.value === 'service');
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
```vue
|
|
55
|
-
<Field name="productType">
|
|
56
|
-
<VcSelect
|
|
57
|
-
v-model="productType"
|
|
58
|
-
:options="['Physical', 'Digital', 'Service']"
|
|
59
|
-
/>
|
|
60
|
-
</Field>
|
|
61
|
-
|
|
62
|
-
<!-- Physical fields -->
|
|
63
|
-
<template v-if="showPhysicalFields">
|
|
64
|
-
<Field name="weight">
|
|
65
|
-
<VcInput v-model="item.weight" type="number" />
|
|
66
|
-
</Field>
|
|
67
|
-
<!-- ... more physical fields -->
|
|
68
|
-
</template>
|
|
69
|
-
|
|
70
|
-
<!-- Digital fields -->
|
|
71
|
-
<template v-if="showDigitalFields">
|
|
72
|
-
<Field name="downloadUrl">
|
|
73
|
-
<VcInput v-model="item.downloadUrl" />
|
|
74
|
-
</Field>
|
|
75
|
-
<!-- ... more digital fields -->
|
|
76
|
-
</template>
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## Scenario 2: Master-Detail Relationship
|
|
82
|
-
|
|
83
|
-
**Use Case:** Edit item with related items (e.g., Order with Order Items)
|
|
84
|
-
|
|
85
|
-
**Prompt for AI:**
|
|
86
|
-
```
|
|
87
|
-
Create an order management system with master-detail relationship.
|
|
88
|
-
|
|
89
|
-
Master (Order):
|
|
90
|
-
- Order number
|
|
91
|
-
- Customer
|
|
92
|
-
- Order date
|
|
93
|
-
- Status
|
|
94
|
-
- Notes
|
|
95
|
-
|
|
96
|
-
Detail (Order Items) - editable table:
|
|
97
|
-
- Product (select from products list)
|
|
98
|
-
- Quantity (number)
|
|
99
|
-
- Unit price (currency, auto-filled from product)
|
|
100
|
-
- Discount (percentage)
|
|
101
|
-
- Total (calculated: quantity × price × (1 - discount))
|
|
102
|
-
- Actions: Remove item
|
|
103
|
-
|
|
104
|
-
Features:
|
|
105
|
-
- Add item button (opens product selector)
|
|
106
|
-
- Remove item button (with confirmation)
|
|
107
|
-
- Subtotal (sum of all item totals)
|
|
108
|
-
- Tax (10% of subtotal)
|
|
109
|
-
- Shipping cost (manual entry)
|
|
110
|
-
- Grand total (subtotal + tax + shipping)
|
|
111
|
-
- All totals auto-calculate on any change
|
|
112
|
-
- Validate: at least one item required
|
|
113
|
-
- Validate: quantity > 0
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
**Expected Implementation:**
|
|
117
|
-
```typescript
|
|
118
|
-
// Order items array
|
|
119
|
-
const items = ref<OrderItem[]>([]);
|
|
120
|
-
|
|
121
|
-
// Calculated totals
|
|
122
|
-
const subtotal = computed(() =>
|
|
123
|
-
items.value.reduce((sum, item) => {
|
|
124
|
-
const discount = item.discount || 0;
|
|
125
|
-
return sum + (item.quantity * item.unitPrice * (1 - discount / 100));
|
|
126
|
-
}, 0)
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const tax = computed(() => subtotal.value * 0.1);
|
|
130
|
-
|
|
131
|
-
const grandTotal = computed(() =>
|
|
132
|
-
subtotal.value + tax.value + (order.value.shipping || 0)
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
// Add item
|
|
136
|
-
const addItem = (product: Product) => {
|
|
137
|
-
items.value.push({
|
|
138
|
-
productId: product.id,
|
|
139
|
-
productName: product.name,
|
|
140
|
-
quantity: 1,
|
|
141
|
-
unitPrice: product.price,
|
|
142
|
-
discount: 0
|
|
143
|
-
});
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
// Remove item
|
|
147
|
-
const removeItem = (index: number) => {
|
|
148
|
-
items.value.splice(index, 1);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
// Watch for changes and update order
|
|
152
|
-
watch([subtotal, tax, grandTotal], () => {
|
|
153
|
-
order.value.subtotal = subtotal.value;
|
|
154
|
-
order.value.tax = tax.value;
|
|
155
|
-
order.value.total = grandTotal.value;
|
|
156
|
-
});
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## Scenario 3: Multi-Step Wizard
|
|
162
|
-
|
|
163
|
-
**Use Case:** Complex form split into steps
|
|
164
|
-
|
|
165
|
-
**Prompt for AI:**
|
|
166
|
-
```
|
|
167
|
-
Create a product onboarding wizard with 4 steps.
|
|
168
|
-
|
|
169
|
-
Step 1: Basic Info
|
|
170
|
-
- Product name (text, required)
|
|
171
|
-
- Category (select, required)
|
|
172
|
-
- Brand (text)
|
|
173
|
-
|
|
174
|
-
Step 2: Pricing
|
|
175
|
-
- Cost price (currency, required)
|
|
176
|
-
- Selling price (currency, required, must be > cost price)
|
|
177
|
-
- Compare at price (currency, optional)
|
|
178
|
-
- Margin (calculated, read-only)
|
|
179
|
-
|
|
180
|
-
Step 3: Inventory
|
|
181
|
-
- SKU (text, required, unique)
|
|
182
|
-
- Quantity (number, required, min 0)
|
|
183
|
-
- Track inventory (checkbox)
|
|
184
|
-
- Low stock alert (number, shown only if track inventory)
|
|
185
|
-
|
|
186
|
-
Step 4: Media
|
|
187
|
-
- Images (gallery, drag to reorder)
|
|
188
|
-
- Videos (file upload, mp4)
|
|
189
|
-
- Documents (file upload, pdf)
|
|
190
|
-
|
|
191
|
-
Navigation:
|
|
192
|
-
- Next button (disabled if current step invalid)
|
|
193
|
-
- Previous button (enabled except on step 1)
|
|
194
|
-
- Save draft button (available on all steps)
|
|
195
|
-
- Finish button (only on step 4, saves and closes)
|
|
196
|
-
|
|
197
|
-
Progress indicator at top showing: Step 1 of 4, Step 2 of 4, etc.
|
|
198
|
-
Validate each step before allowing next.
|
|
199
|
-
Show step completion status (completed, current, upcoming).
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Expected Implementation:**
|
|
203
|
-
```typescript
|
|
204
|
-
const currentStep = ref(1);
|
|
205
|
-
const totalSteps = 4;
|
|
206
|
-
|
|
207
|
-
const steps = [
|
|
208
|
-
{ id: 1, title: 'Basic Info', completed: false },
|
|
209
|
-
{ id: 2, title: 'Pricing', completed: false },
|
|
210
|
-
{ id: 3, title: 'Inventory', completed: false },
|
|
211
|
-
{ id: 4, title: 'Media', completed: false }
|
|
212
|
-
];
|
|
213
|
-
|
|
214
|
-
const canGoNext = computed(() => {
|
|
215
|
-
switch(currentStep.value) {
|
|
216
|
-
case 1:
|
|
217
|
-
return !!item.value.name && !!item.value.category;
|
|
218
|
-
case 2:
|
|
219
|
-
return item.value.sellingPrice > item.value.costPrice;
|
|
220
|
-
case 3:
|
|
221
|
-
return !!item.value.sku && item.value.quantity >= 0;
|
|
222
|
-
default:
|
|
223
|
-
return true;
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const nextStep = () => {
|
|
228
|
-
if (canGoNext.value && currentStep.value < totalSteps) {
|
|
229
|
-
steps[currentStep.value - 1].completed = true;
|
|
230
|
-
currentStep.value++;
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
const previousStep = () => {
|
|
235
|
-
if (currentStep.value > 1) {
|
|
236
|
-
currentStep.value--;
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
const saveDraft = async () => {
|
|
241
|
-
item.value.status = 'draft';
|
|
242
|
-
await api.save(item.value);
|
|
243
|
-
notification.success('Draft saved');
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
const finish = async () => {
|
|
247
|
-
item.value.status = 'active';
|
|
248
|
-
await api.save(item.value);
|
|
249
|
-
notification.success('Product created');
|
|
250
|
-
closeBlade();
|
|
251
|
-
};
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## Scenario 4: Advanced Validation
|
|
257
|
-
|
|
258
|
-
**Use Case:** Complex validation rules and cross-field validation
|
|
259
|
-
|
|
260
|
-
**Prompt for AI:**
|
|
261
|
-
```
|
|
262
|
-
Create a form with advanced validation rules.
|
|
263
|
-
|
|
264
|
-
Fields:
|
|
265
|
-
- Email (required, valid email format)
|
|
266
|
-
- Password (required, min 8 chars, must have uppercase, lowercase, number, special char)
|
|
267
|
-
- Confirm password (must match password)
|
|
268
|
-
- Age (required, must be 18-100)
|
|
269
|
-
- Phone (required, format: (XXX) XXX-XXXX)
|
|
270
|
-
- Website (optional, valid URL if provided)
|
|
271
|
-
- Start date (required)
|
|
272
|
-
- End date (required, must be after start date)
|
|
273
|
-
- Price (required, min 0, max 999999.99)
|
|
274
|
-
- Quantity (required, integer, min 1)
|
|
275
|
-
|
|
276
|
-
Custom validators:
|
|
277
|
-
- Email must not be already registered (check with API)
|
|
278
|
-
- Username must be unique (debounced check)
|
|
279
|
-
- Start and end dates cannot be on weekends
|
|
280
|
-
|
|
281
|
-
Show errors:
|
|
282
|
-
- Inline below field
|
|
283
|
-
- Summary at top of form
|
|
284
|
-
- Highlight invalid fields in red
|
|
285
|
-
|
|
286
|
-
Disable save button while any validation errors exist.
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
**Expected Implementation:**
|
|
290
|
-
```typescript
|
|
291
|
-
import { useForm, defineRule } from 'vee-validate';
|
|
292
|
-
|
|
293
|
-
// Custom rule: password strength
|
|
294
|
-
defineRule('strongPassword', (value: string) => {
|
|
295
|
-
if (!value) return 'Password is required';
|
|
296
|
-
if (value.length < 8) return 'At least 8 characters';
|
|
297
|
-
if (!/[A-Z]/.test(value)) return 'At least one uppercase';
|
|
298
|
-
if (!/[a-z]/.test(value)) return 'At least one lowercase';
|
|
299
|
-
if (!/[0-9]/.test(value)) return 'At least one number';
|
|
300
|
-
if (!/[^A-Za-z0-9]/.test(value)) return 'At least one special character';
|
|
301
|
-
return true;
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
// Custom rule: date after
|
|
305
|
-
defineRule('afterDate', (value: string, [target]: string[]) => {
|
|
306
|
-
const valueDate = new Date(value);
|
|
307
|
-
const targetDate = new Date(target);
|
|
308
|
-
return valueDate > targetDate || 'Must be after start date';
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
// Custom rule: async unique check
|
|
312
|
-
defineRule('uniqueEmail', async (value: string) => {
|
|
313
|
-
const exists = await api.checkEmailExists(value);
|
|
314
|
-
return !exists || 'Email already registered';
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
const validationSchema = {
|
|
318
|
-
email: { required: true, email: true, uniqueEmail: true },
|
|
319
|
-
password: { required: true, strongPassword: true },
|
|
320
|
-
confirmPassword: { required: true, confirmed: '@password' },
|
|
321
|
-
age: { required: true, min_value: 18, max_value: 100 },
|
|
322
|
-
phone: { required: true, regex: /^\(\d{3}\) \d{3}-\d{4}$/ },
|
|
323
|
-
website: { url: true },
|
|
324
|
-
startDate: { required: true },
|
|
325
|
-
endDate: { required: true, afterDate: '@startDate' },
|
|
326
|
-
price: { required: true, min_value: 0, max_value: 999999.99 },
|
|
327
|
-
quantity: { required: true, integer: true, min_value: 1 }
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
const { validate, errors, isValidating } = useForm({ validationSchema });
|
|
331
|
-
|
|
332
|
-
const canSave = computed(() =>
|
|
333
|
-
!isValidating.value && Object.keys(errors.value).length === 0
|
|
334
|
-
);
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
## Scenario 5: File Upload with Preview
|
|
340
|
-
|
|
341
|
-
**Use Case:** Upload multiple files with preview and processing
|
|
342
|
-
|
|
343
|
-
**Prompt for AI:**
|
|
344
|
-
```
|
|
345
|
-
Create a product gallery manager with advanced file upload.
|
|
346
|
-
|
|
347
|
-
Features:
|
|
348
|
-
- Drag & drop files
|
|
349
|
-
- Multiple file selection
|
|
350
|
-
- Image preview (thumbnails)
|
|
351
|
-
- File type validation (jpg, png, webp only)
|
|
352
|
-
- File size limit (max 5MB per file)
|
|
353
|
-
- Image dimensions validation (min 800x600)
|
|
354
|
-
- Upload progress bar per file
|
|
355
|
-
- Cancel upload button
|
|
356
|
-
- Remove uploaded image button
|
|
357
|
-
- Reorder images (drag & drop)
|
|
358
|
-
- Set primary image (star icon)
|
|
359
|
-
- Edit image (crop, rotate, resize)
|
|
360
|
-
- Bulk actions (select multiple, delete selected)
|
|
361
|
-
- Total storage used indicator
|
|
362
|
-
|
|
363
|
-
Show during upload:
|
|
364
|
-
- Filename
|
|
365
|
-
- File size
|
|
366
|
-
- Progress percentage
|
|
367
|
-
- Upload speed
|
|
368
|
-
- Estimated time remaining
|
|
369
|
-
|
|
370
|
-
After upload:
|
|
371
|
-
- Thumbnail
|
|
372
|
-
- Filename
|
|
373
|
-
- File size
|
|
374
|
-
- Dimensions
|
|
375
|
-
- Actions (edit, delete, set as primary)
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
**Expected Implementation:**
|
|
379
|
-
```typescript
|
|
380
|
-
interface UploadFile {
|
|
381
|
-
id: string;
|
|
382
|
-
file: File;
|
|
383
|
-
preview: string;
|
|
384
|
-
progress: number;
|
|
385
|
-
status: 'pending' | 'uploading' | 'completed' | 'error';
|
|
386
|
-
error?: string;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const uploads = ref<UploadFile[]>([]);
|
|
390
|
-
const existingImages = ref<Image[]>([]);
|
|
391
|
-
|
|
392
|
-
// Handle file selection
|
|
393
|
-
const handleFileSelect = async (files: FileList) => {
|
|
394
|
-
for (const file of Array.from(files)) {
|
|
395
|
-
// Validate
|
|
396
|
-
if (!validateFile(file)) continue;
|
|
397
|
-
|
|
398
|
-
// Create preview
|
|
399
|
-
const preview = await createPreview(file);
|
|
400
|
-
|
|
401
|
-
// Add to uploads
|
|
402
|
-
uploads.value.push({
|
|
403
|
-
id: crypto.randomUUID(),
|
|
404
|
-
file,
|
|
405
|
-
preview,
|
|
406
|
-
progress: 0,
|
|
407
|
-
status: 'pending'
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Start uploads
|
|
412
|
-
processUploads();
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
// Validate file
|
|
416
|
-
const validateFile = (file: File): boolean => {
|
|
417
|
-
// Type check
|
|
418
|
-
if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
|
|
419
|
-
notification.error(`Invalid file type: ${file.name}`);
|
|
420
|
-
return false;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Size check
|
|
424
|
-
if (file.size > 5 * 1024 * 1024) {
|
|
425
|
-
notification.error(`File too large: ${file.name} (max 5MB)`);
|
|
426
|
-
return false;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return true;
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
// Create preview
|
|
433
|
-
const createPreview = (file: File): Promise<string> => {
|
|
434
|
-
return new Promise((resolve) => {
|
|
435
|
-
const reader = new FileReader();
|
|
436
|
-
reader.onload = (e) => resolve(e.target?.result as string);
|
|
437
|
-
reader.readAsDataURL(file);
|
|
438
|
-
});
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
// Upload files
|
|
442
|
-
const processUploads = async () => {
|
|
443
|
-
for (const upload of uploads.value) {
|
|
444
|
-
if (upload.status !== 'pending') continue;
|
|
445
|
-
|
|
446
|
-
upload.status = 'uploading';
|
|
447
|
-
|
|
448
|
-
try {
|
|
449
|
-
const formData = new FormData();
|
|
450
|
-
formData.append('file', upload.file);
|
|
451
|
-
|
|
452
|
-
const result = await api.uploadImage(formData, {
|
|
453
|
-
onUploadProgress: (event) => {
|
|
454
|
-
upload.progress = Math.round((event.loaded / event.total) * 100);
|
|
455
|
-
}
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
upload.status = 'completed';
|
|
459
|
-
existingImages.value.push(result.data);
|
|
460
|
-
|
|
461
|
-
} catch (error) {
|
|
462
|
-
upload.status = 'error';
|
|
463
|
-
upload.error = error.message;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
// Reorder images
|
|
469
|
-
const reorderImages = (fromIndex: number, toIndex: number) => {
|
|
470
|
-
const item = existingImages.value.splice(fromIndex, 1)[0];
|
|
471
|
-
existingImages.value.splice(toIndex, 0, item);
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
// Set primary image
|
|
475
|
-
const setPrimary = (imageId: string) => {
|
|
476
|
-
existingImages.value.forEach(img => {
|
|
477
|
-
img.isPrimary = img.id === imageId;
|
|
478
|
-
});
|
|
479
|
-
};
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
---
|
|
483
|
-
|
|
484
|
-
## Scenario 6: Real-Time Updates
|
|
485
|
-
|
|
486
|
-
**Use Case:** Display real-time data updates using WebSocket or polling
|
|
487
|
-
|
|
488
|
-
**Prompt for AI:**
|
|
489
|
-
```
|
|
490
|
-
Create an order monitoring dashboard with real-time updates.
|
|
491
|
-
|
|
492
|
-
Display:
|
|
493
|
-
- Total orders today (updates every second)
|
|
494
|
-
- Recent orders table (updates when new order arrives)
|
|
495
|
-
- Order status chart (updates when status changes)
|
|
496
|
-
- Active users count (live)
|
|
497
|
-
- Revenue today (live)
|
|
498
|
-
|
|
499
|
-
Implementation:
|
|
500
|
-
- Use WebSocket for real-time updates
|
|
501
|
-
- Fallback to polling if WebSocket unavailable
|
|
502
|
-
- Show connection status indicator
|
|
503
|
-
- Auto-reconnect on disconnect
|
|
504
|
-
- Smooth animations for value changes
|
|
505
|
-
- Flash highlight when data updates
|
|
506
|
-
- Sound notification for new orders (optional)
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
**Expected Implementation:**
|
|
510
|
-
```typescript
|
|
511
|
-
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
|
512
|
-
|
|
513
|
-
let websocket: WebSocket | null = null;
|
|
514
|
-
let reconnectTimeout: number;
|
|
515
|
-
|
|
516
|
-
const isConnected = ref(false);
|
|
517
|
-
const stats = ref({
|
|
518
|
-
ordersToday: 0,
|
|
519
|
-
activeUsers: 0,
|
|
520
|
-
revenueToday: 0
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
const recentOrders = ref<Order[]>([]);
|
|
524
|
-
|
|
525
|
-
// Connect to WebSocket
|
|
526
|
-
const connect = () => {
|
|
527
|
-
websocket = new WebSocket('wss://api.example.com/ws');
|
|
528
|
-
|
|
529
|
-
websocket.onopen = () => {
|
|
530
|
-
isConnected.value = true;
|
|
531
|
-
console.log('WebSocket connected');
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
websocket.onmessage = (event) => {
|
|
535
|
-
const data = JSON.parse(event.data);
|
|
536
|
-
handleUpdate(data);
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
websocket.onerror = (error) => {
|
|
540
|
-
console.error('WebSocket error:', error);
|
|
541
|
-
};
|
|
542
|
-
|
|
543
|
-
websocket.onclose = () => {
|
|
544
|
-
isConnected.value = false;
|
|
545
|
-
console.log('WebSocket disconnected');
|
|
546
|
-
|
|
547
|
-
// Auto-reconnect after 5 seconds
|
|
548
|
-
reconnectTimeout = setTimeout(connect, 5000);
|
|
549
|
-
};
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
// Handle incoming updates
|
|
553
|
-
const handleUpdate = (data: any) => {
|
|
554
|
-
switch (data.type) {
|
|
555
|
-
case 'stats':
|
|
556
|
-
// Animate value change
|
|
557
|
-
animateValue(stats.value, 'ordersToday', data.ordersToday);
|
|
558
|
-
animateValue(stats.value, 'activeUsers', data.activeUsers);
|
|
559
|
-
animateValue(stats.value, 'revenueToday', data.revenueToday);
|
|
560
|
-
break;
|
|
561
|
-
|
|
562
|
-
case 'newOrder':
|
|
563
|
-
// Add to recent orders
|
|
564
|
-
recentOrders.value.unshift(data.order);
|
|
565
|
-
if (recentOrders.value.length > 10) {
|
|
566
|
-
recentOrders.value.pop();
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// Flash highlight
|
|
570
|
-
flashHighlight(data.order.id);
|
|
571
|
-
|
|
572
|
-
// Play sound
|
|
573
|
-
playNotificationSound();
|
|
574
|
-
break;
|
|
575
|
-
|
|
576
|
-
case 'statusChange':
|
|
577
|
-
// Update order status
|
|
578
|
-
const order = recentOrders.value.find(o => o.id === data.orderId);
|
|
579
|
-
if (order) {
|
|
580
|
-
order.status = data.newStatus;
|
|
581
|
-
flashHighlight(data.orderId);
|
|
582
|
-
}
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
// Animate value change
|
|
588
|
-
const animateValue = (obj: any, key: string, target: number) => {
|
|
589
|
-
const start = obj[key];
|
|
590
|
-
const duration = 1000; // 1 second
|
|
591
|
-
const startTime = Date.now();
|
|
592
|
-
|
|
593
|
-
const animate = () => {
|
|
594
|
-
const elapsed = Date.now() - startTime;
|
|
595
|
-
const progress = Math.min(elapsed / duration, 1);
|
|
596
|
-
|
|
597
|
-
obj[key] = Math.round(start + (target - start) * progress);
|
|
598
|
-
|
|
599
|
-
if (progress < 1) {
|
|
600
|
-
requestAnimationFrame(animate);
|
|
601
|
-
}
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
animate();
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
// Fallback to polling
|
|
608
|
-
const startPolling = () => {
|
|
609
|
-
setInterval(async () => {
|
|
610
|
-
if (!isConnected.value) {
|
|
611
|
-
const data = await api.getStats();
|
|
612
|
-
stats.value = data;
|
|
613
|
-
}
|
|
614
|
-
}, 5000);
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
// Lifecycle
|
|
618
|
-
onMounted(() => {
|
|
619
|
-
connect();
|
|
620
|
-
startPolling();
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
onBeforeUnmount(() => {
|
|
624
|
-
if (websocket) {
|
|
625
|
-
websocket.close();
|
|
626
|
-
}
|
|
627
|
-
clearTimeout(reconnectTimeout);
|
|
628
|
-
});
|
|
629
|
-
```
|
|
630
|
-
|
|
631
|
-
---
|
|
632
|
-
|
|
633
|
-
## Scenario 7: Inline Editing Table
|
|
634
|
-
|
|
635
|
-
**Use Case:** Edit table cells directly without opening form
|
|
636
|
-
|
|
637
|
-
**Prompt for AI:**
|
|
638
|
-
```
|
|
639
|
-
Create a product price management grid with inline editing.
|
|
640
|
-
|
|
641
|
-
Table columns:
|
|
642
|
-
- Product name (read-only)
|
|
643
|
-
- SKU (read-only)
|
|
644
|
-
- Cost price (editable, currency)
|
|
645
|
-
- Selling price (editable, currency)
|
|
646
|
-
- Margin % (calculated, read-only)
|
|
647
|
-
- Stock (editable, number)
|
|
648
|
-
- Status (editable, select: active, inactive)
|
|
649
|
-
- Actions (save, cancel)
|
|
650
|
-
|
|
651
|
-
Features:
|
|
652
|
-
- Click cell to edit (input appears)
|
|
653
|
-
- Press Enter to save
|
|
654
|
-
- Press Escape to cancel
|
|
655
|
-
- Click outside cell to save
|
|
656
|
-
- Show loading indicator while saving
|
|
657
|
-
- Highlight row when editing
|
|
658
|
-
- Validate on blur
|
|
659
|
-
- Show error message inline
|
|
660
|
-
- Undo changes button
|
|
661
|
-
- Bulk edit selected rows
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
**Expected Implementation:**
|
|
665
|
-
```typescript
|
|
666
|
-
const editingCell = ref<{rowId: string; field: string} | null>(null);
|
|
667
|
-
const originalValues = new Map();
|
|
668
|
-
|
|
669
|
-
const startEdit = (rowId: string, field: string, currentValue: any) => {
|
|
670
|
-
editingCell.value = { rowId, field };
|
|
671
|
-
originalValues.set(`${rowId}-${field}`, currentValue);
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
const saveEdit = async (row: any, field: string) => {
|
|
675
|
-
try {
|
|
676
|
-
await api.updateProduct(row.id, { [field]: row[field] });
|
|
677
|
-
notification.success('Updated');
|
|
678
|
-
editingCell.value = null;
|
|
679
|
-
originalValues.delete(`${row.id}-${field}`);
|
|
680
|
-
} catch (error) {
|
|
681
|
-
notification.error('Failed to update');
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
|
|
685
|
-
const cancelEdit = (row: any, field: string) => {
|
|
686
|
-
const originalValue = originalValues.get(`${row.id}-${field}`);
|
|
687
|
-
if (originalValue !== undefined) {
|
|
688
|
-
row[field] = originalValue;
|
|
689
|
-
}
|
|
690
|
-
editingCell.value = null;
|
|
691
|
-
originalValues.delete(`${row.id}-${field}`);
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
const isEditing = (rowId: string, field: string) => {
|
|
695
|
-
return editingCell.value?.rowId === rowId &&
|
|
696
|
-
editingCell.value?.field === field;
|
|
697
|
-
};
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
```vue
|
|
701
|
-
<template>
|
|
702
|
-
<VcTable :items="products">
|
|
703
|
-
<template #item_price="{ item }">
|
|
704
|
-
<div v-if="isEditing(item.id, 'price')">
|
|
705
|
-
<VcInputCurrency
|
|
706
|
-
v-model="item.price"
|
|
707
|
-
@blur="saveEdit(item, 'price')"
|
|
708
|
-
@keyup.enter="saveEdit(item, 'price')"
|
|
709
|
-
@keyup.esc="cancelEdit(item, 'price')"
|
|
710
|
-
autofocus
|
|
711
|
-
/>
|
|
712
|
-
</div>
|
|
713
|
-
<div
|
|
714
|
-
v-else
|
|
715
|
-
@click="startEdit(item.id, 'price', item.price)"
|
|
716
|
-
class="editable-cell"
|
|
717
|
-
>
|
|
718
|
-
{{ formatCurrency(item.price) }}
|
|
719
|
-
</div>
|
|
720
|
-
</template>
|
|
721
|
-
</VcTable>
|
|
722
|
-
</template>
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
---
|
|
726
|
-
|
|
727
|
-
## Scenario 8: Conditional Workflow
|
|
728
|
-
|
|
729
|
-
**Use Case:** Approval workflow with different paths
|
|
730
|
-
|
|
731
|
-
**Prompt for AI:**
|
|
732
|
-
```
|
|
733
|
-
Create a document approval system with conditional workflow.
|
|
734
|
-
|
|
735
|
-
Document states: Draft → Submitted → Approved/Rejected → Published
|
|
736
|
-
|
|
737
|
-
Rules:
|
|
738
|
-
- Author can: Create, Edit (if Draft), Submit, Cancel
|
|
739
|
-
- Reviewer can: Approve, Reject, Request Changes
|
|
740
|
-
- Admin can: Override, Publish, Archive
|
|
741
|
-
|
|
742
|
-
Workflow:
|
|
743
|
-
1. Author creates document (Draft)
|
|
744
|
-
2. Author submits for review (Submitted)
|
|
745
|
-
3. If approved → can publish
|
|
746
|
-
4. If rejected → back to Draft
|
|
747
|
-
5. If changes requested → back to Draft with comments
|
|
748
|
-
6. Admin can override any state
|
|
749
|
-
|
|
750
|
-
Show:
|
|
751
|
-
- Current state badge
|
|
752
|
-
- Available actions (buttons) based on user role and state
|
|
753
|
-
- History timeline (state changes, who did what, when)
|
|
754
|
-
- Comments thread
|
|
755
|
-
- Approval/rejection reason required
|
|
756
|
-
|
|
757
|
-
Implement state machine pattern.
|
|
758
|
-
Validate state transitions.
|
|
759
|
-
Send notifications on state changes.
|
|
760
|
-
```
|
|
761
|
-
|
|
762
|
-
**Expected Implementation:**
|
|
763
|
-
```typescript
|
|
764
|
-
type DocumentState = 'draft' | 'submitted' | 'approved' | 'rejected' | 'published' | 'archived';
|
|
765
|
-
type UserRole = 'author' | 'reviewer' | 'admin';
|
|
766
|
-
|
|
767
|
-
interface StateTransition {
|
|
768
|
-
from: DocumentState;
|
|
769
|
-
to: DocumentState;
|
|
770
|
-
roles: UserRole[];
|
|
771
|
-
requiresComment?: boolean;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
const transitions: StateTransition[] = [
|
|
775
|
-
{ from: 'draft', to: 'submitted', roles: ['author'] },
|
|
776
|
-
{ from: 'submitted', to: 'approved', roles: ['reviewer', 'admin'] },
|
|
777
|
-
{ from: 'submitted', to: 'rejected', roles: ['reviewer', 'admin'], requiresComment: true },
|
|
778
|
-
{ from: 'approved', to: 'published', roles: ['author', 'admin'] },
|
|
779
|
-
{ from: 'rejected', to: 'draft', roles: ['author'] },
|
|
780
|
-
// Admin overrides
|
|
781
|
-
{ from: 'draft', to: 'published', roles: ['admin'] },
|
|
782
|
-
{ from: 'published', to: 'archived', roles: ['admin'] }
|
|
783
|
-
];
|
|
784
|
-
|
|
785
|
-
const canTransition = (from: DocumentState, to: DocumentState, role: UserRole): boolean => {
|
|
786
|
-
return transitions.some(t =>
|
|
787
|
-
t.from === from && t.to === to && t.roles.includes(role)
|
|
788
|
-
);
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
const availableActions = computed(() => {
|
|
792
|
-
const currentState = document.value.state;
|
|
793
|
-
const userRole = currentUser.value.role;
|
|
794
|
-
|
|
795
|
-
return transitions
|
|
796
|
-
.filter(t => t.from === currentState && t.roles.includes(userRole))
|
|
797
|
-
.map(t => ({
|
|
798
|
-
action: t.to,
|
|
799
|
-
label: getActionLabel(t.to),
|
|
800
|
-
requiresComment: t.requiresComment
|
|
801
|
-
}));
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
const performTransition = async (targetState: DocumentState, comment?: string) => {
|
|
805
|
-
if (!canTransition(document.value.state, targetState, currentUser.value.role)) {
|
|
806
|
-
notification.error('Action not allowed');
|
|
807
|
-
return;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
const transition = transitions.find(t =>
|
|
811
|
-
t.from === document.value.state && t.to === targetState
|
|
812
|
-
);
|
|
813
|
-
|
|
814
|
-
if (transition?.requiresComment && !comment) {
|
|
815
|
-
notification.error('Comment required for this action');
|
|
816
|
-
return;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
try {
|
|
820
|
-
await api.updateDocumentState(document.value.id, {
|
|
821
|
-
newState: targetState,
|
|
822
|
-
comment,
|
|
823
|
-
userId: currentUser.value.id
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
document.value.state = targetState;
|
|
827
|
-
|
|
828
|
-
// Add to history
|
|
829
|
-
document.value.history.push({
|
|
830
|
-
timestamp: new Date(),
|
|
831
|
-
user: currentUser.value.name,
|
|
832
|
-
action: targetState,
|
|
833
|
-
comment
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
notification.success(`Document ${targetState}`);
|
|
837
|
-
|
|
838
|
-
} catch (error) {
|
|
839
|
-
notification.error('Failed to update state');
|
|
840
|
-
}
|
|
841
|
-
};
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
---
|
|
845
|
-
|
|
846
|
-
## Related Documentation
|
|
847
|
-
|
|
848
|
-
- [CLI Usage](./cli-usage.md) - Generate modules
|
|
849
|
-
- [Quick Start Scenarios](./quick-start-scenarios.md) - Common modules
|
|
850
|
-
- [Complete Workflow](../guides/complete-workflow.md) - Development process
|
|
851
|
-
- [Troubleshooting Guide](../guides/troubleshooting.md) - Solve common issues
|
|
852
|
-
|