Files
interview-demo-code/.cursor/rules/vue.md
Nikita Kiselev 9a93cc7342 feat: add Telegram customers management system with admin panel
Implement comprehensive Telegram customers storage and management functionality:

Backend:
- Add database migration for telecart_customers table with indexes
- Create TelegramCustomer model with CRUD operations
- Implement TelegramCustomerService for business logic
- Add TelegramCustomerHandler for API endpoint (saveOrUpdate)
- Add TelegramCustomersHandler for admin API (getCustomers with pagination, filtering, sorting)
- Add SendMessageHandler for sending messages to customers via Telegram
- Create custom exceptions: TelegramCustomerNotFoundException, TelegramCustomerWriteNotAllowedException
- Refactor TelegramInitDataDecoder to separate decoding logic
- Add TelegramHeader enum for header constants
- Update SignatureValidator to use TelegramInitDataDecoder
- Register new routes in bastion/routes.php and src/routes.php

Frontend (Admin):
- Add CustomersView.vue component with PrimeVue DataTable
- Implement advanced filtering (text, date, boolean filters)
- Add column visibility toggle functionality
- Add global search with debounce
- Implement message sending dialog with validation
- Add Russian locale for PrimeVue components
- Add navigation link in App.vue
- Register route in router

Frontend (SPA):
- Add saveTelegramCustomer utility function
- Integrate automatic customer data saving on app initialization
- Extract user data from Telegram.WebApp.initDataUnsafe

The system automatically saves/updates customer data when users access the Telegram Mini App,
and provides admin interface for viewing, filtering, and messaging customers.

BREAKING CHANGE: None
2025-11-23 21:30:51 +03:00

371 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Vue.js 3 Rules
## Component Structure
### Template
```vue
<template>
<!-- Логическая структура -->
<div class="container">
<header>
<h2>{{ title }}</h2>
</header>
<main>
<DataTable :value="items" />
</main>
<footer>
<Button @click="handleSave">Save</Button>
</footer>
</div>
</template>
```
### Script Setup
```vue
<script setup>
// ✅ Всегда используй <script setup>
import { ref, computed, onMounted } from 'vue';
import DataTable from 'primevue/datatable';
import { apiGet } from '@/utils/http.js';
// Props
const props = defineProps({
title: {
type: String,
required: true
}
});
// Emits
const emit = defineEmits(['update', 'delete']);
// State
const items = ref([]);
const loading = ref(false);
// Computed
const totalItems = computed(() => items.value.length);
// Methods
async function loadItems() {
loading.value = true;
try {
const result = await apiGet('getItems');
items.value = result.data || [];
} finally {
loading.value = false;
}
}
// Lifecycle
onMounted(() => {
loadItems();
});
</script>
```
### Styles
```vue
<style scoped>
/* ✅ Используй scoped стили */
.container {
padding: 1rem;
}
/* ✅ Используй :deep() для доступа к дочерним компонентам */
:deep(.p-datatable) {
border: 1px solid #ccc;
}
</style>
```
## Component Naming
```vue
<!-- PascalCase для компонентов -->
<CustomerCard />
<ProductsList />
<OrderDetails />
<!-- kebab-case в шаблоне тоже работает -->
<customer-card />
```
## Props
```vue
<script setup>
// ✅ Всегда определяй типы и валидацию
const props = defineProps({
customerId: {
type: Number,
required: true,
validator: (value) => value > 0
},
showDetails: {
type: Boolean,
default: false
},
items: {
type: Array,
default: () => []
}
});
</script>
```
## Emits
```vue
<script setup>
// ✅ Определяй emits с типами
const emit = defineEmits<{
update: [id: number, data: object];
delete: [id: number];
cancel: [];
}>();
// ✅ Или с валидацией
const emit = defineEmits({
update: (id: number, data: object) => {
if (id > 0 && typeof data === 'object') {
return true;
}
console.warn('Invalid emit arguments');
return false;
}
});
</script>
```
## Reactive State
```vue
<script setup>
// ✅ ref для примитивов
const count = ref(0);
const name = ref('');
// ✅ ref для объектов (предпочтительно)
const customer = ref({
id: null,
name: '',
email: ''
});
// ✅ reactive только если нужно
import { reactive } from 'vue';
const state = reactive({
items: [],
loading: false
});
</script>
```
## Computed Properties
```vue
<script setup>
// ✅ Используй computed для производных значений
const filteredItems = computed(() => {
return items.value.filter(item => item.isActive);
});
// ✅ Computed с getter/setter
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (value) => {
const parts = value.split(' ');
firstName.value = parts[0];
lastName.value = parts[1];
}
});
</script>
```
## Event Handlers
```vue
<template>
<!-- Используй kebab-case для событий -->
<Button @click="handleClick" />
<Input @input="handleInput" />
<Form @submit.prevent="handleSubmit" />
</template>
<script setup>
// ✅ Именуй обработчики с префиксом handle
function handleClick() {
// ...
}
function handleInput(event) {
// ...
}
function handleSubmit() {
// ...
}
</script>
```
## Conditional Rendering
```vue
<template>
<!-- Используй v-if для условного рендеринга -->
<div v-if="loading">
<LoadingSpinner />
</div>
<!-- v-show для частых переключений -->
<div v-show="hasItems">
<ItemsList :items="items" />
</div>
<!-- v-else для альтернатив -->
<div v-else>
<EmptyState />
</div>
</template>
```
## Lists
```vue
<template>
<!-- Всегда используй :key -->
<div v-for="item in items" :key="item.id">
{{ item.name }}
</div>
<!-- Для индексов -->
<div v-for="(item, index) in items" :key="`item-${index}`">
{{ item.name }}
</div>
</template>
```
## Form Handling
```vue
<template>
<form @submit.prevent="handleSubmit">
<!-- Используй v-model -->
<InputText v-model="form.name" />
<Textarea v-model="form.description" />
<!-- Для кастомных компонентов -->
<CustomInput v-model="form.email" />
</form>
</template>
<script setup>
const form = ref({
name: '',
description: '',
email: ''
});
function handleSubmit() {
// Валидация и отправка
}
</script>
```
## PrimeVue Components
```vue
<template>
<!-- Используй PrimeVue компоненты в админке -->
<DataTable
:value="customers"
:loading="loading"
paginator
:rows="20"
@page="onPage"
>
<Column field="name" header="Name" sortable />
</DataTable>
</template>
```
## Styling
```vue
<style scoped>
/* ✅ Используй scoped -->
.container {
padding: 1rem;
}
/* ✅ :deep() для дочерних компонентов */
:deep(.p-datatable) {
border: 1px solid #ccc;
}
/* ✅ :slotted() для слотов */
:slotted(.header) {
font-weight: bold;
}
</style>
```
## Composition Functions
```vue
<script setup>
// ✅ Выноси сложную логику в composables
import { useCustomers } from '@/composables/useCustomers.js';
const {
customers,
loading,
loadCustomers,
totalRecords
} = useCustomers();
onMounted(() => {
loadCustomers();
});
</script>
```
## Error Handling
```vue
<script setup>
import { useToast } from 'primevue';
const toast = useToast();
async function loadData() {
try {
const result = await apiGet('endpoint');
if (result.success) {
data.value = result.data;
} else {
toast.add({
severity: 'error',
summary: 'Ошибка',
detail: result.error
});
}
} catch (error) {
console.error('Error:', error);
toast.add({
severity: 'error',
summary: 'Ошибка',
detail: 'Не удалось загрузить данные'
});
}
}
</script>
```