🎯 Рекомендуемые коллекции
Балансированные коллекции примеров кода из различных категорий, которые вы можете исследовать
Vue.js Samples
Vue.js component examples including Composition API, Options API, reactive state, and modern Vue patterns
💻 Vue.js Hello World javascript
🟢 simple
⭐
Basic Vue.js component examples and Hello World applications with template syntax
⏱️ 15 min
🏷️ vue, components, template, reactivity
Prerequisites:
JavaScript basics, HTML basics
// Vue.js Hello World Examples
// 1. Basic Vue 3 app with Composition API
import { createApp } from 'vue';
const HelloWorld = {
template: `
<div>
<h1>Hello, World!</h1>
<p>This is a Vue.js component.</p>
</div>
`
};
createApp(HelloWorld).mount('#app');
// 2. Vue component with reactive data (Composition API)
import { createApp, ref } from 'vue';
const Counter = {
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
return {
count,
increment,
decrement
};
},
template: `
<div>
<h1>Count: {{ count }}</h1>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
`
};
// 3. Vue component with Options API
const UserForm = {
data() {
return {
name: '',
email: '',
submitted: false
};
},
methods: {
submitForm() {
if (this.name && this.email) {
this.submitted = true;
setTimeout(() => {
this.submitted = false;
this.name = '';
this.email = '';
}, 3000);
}
}
},
template: `
<div>
<h2>User Form</h2>
<form @submit.prevent="submitForm">
<div>
<label>Name:</label>
<input v-model="name" type="text" required>
</div>
<div>
<label>Email:</label>
<input v-model="email" type="email" required>
</div>
<button type="submit">Submit</button>
</form>
<div v-if="submitted" class="success">
Form submitted successfully! Name: {{ name }}, Email: {{ email }}
</div>
</div>
`
};
// 4. Conditional rendering and list rendering
const TodoList = {
data() {
return {
todos: [
{ id: 1, text: 'Learn Vue.js', completed: false },
{ id: 2, text: 'Build a project', completed: false },
{ id: 3, text: 'Deploy to production', completed: true }
],
newTodo: '',
filter: 'all'
};
},
computed: {
filteredTodos() {
switch (this.filter) {
case 'completed':
return this.todos.filter(todo => todo.completed);
case 'active':
return this.todos.filter(todo => !todo.completed);
default:
return this.todos;
}
},
activeTodosCount() {
return this.todos.filter(todo => !todo.completed).length;
}
},
methods: {
addTodo() {
if (this.newTodo.trim()) {
this.todos.push({
id: Date.now(),
text: this.newTodo.trim(),
completed: false
});
this.newTodo = '';
}
},
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
},
deleteTodo(id) {
this.todos = this.todos.filter(t => t.id !== id);
}
},
template: `
<div>
<h2>Todo List ({{ activeTodosCount }} remaining)</h2>
<div class="todo-form">
<input
v-model="newTodo"
@keyup.enter="addTodo"
placeholder="Add new todo..."
>
<button @click="addTodo">Add</button>
</div>
<div class="filters">
<button
@click="filter = 'all'"
:class="{ active: filter === 'all' }"
>
All
</button>
<button
@click="filter = 'active'"
:class="{ active: filter === 'active' }"
>
Active
</button>
<button
@click="filter = 'completed'"
:class="{ active: filter === 'completed' }"
>
Completed
</button>
</div>
<ul class="todo-list">
<li
v-for="todo in filteredTodos"
:key="todo.id"
:class="{ completed: todo.completed }"
>
<input
type="checkbox"
v-model="todo.completed"
@change="toggleTodo(todo.id)"
>
<span>{{ todo.text }}</span>
<button @click="deleteTodo(todo.id)" class="delete">Delete</button>
</li>
</ul>
</div>
`
};
// 5. Component with computed properties and watchers
const PriceCalculator = {
data() {
return {
price: 100,
quantity: 1,
taxRate: 0.08,
discountCode: '',
discounts: {
'SAVE10': 0.1,
'SAVE20': 0.2,
'SAVE30': 0.3
}
};
},
computed: {
subtotal() {
return this.price * this.quantity;
},
discount() {
if (this.discounts[this.discountCode.toUpperCase()]) {
return this.subtotal * this.discounts[this.discountCode.toUpperCase()];
}
return 0;
},
discountedSubtotal() {
return this.subtotal - this.discount;
},
tax() {
return this.discountedSubtotal * this.taxRate;
},
total() {
return this.discountedSubtotal + this.tax;
}
},
watch: {
discountCode(newCode) {
if (newCode && !this.discounts[newCode.toUpperCase()]) {
console.log('Invalid discount code');
}
}
},
methods: {
applyDiscount(code) {
this.discountCode = code;
}
},
template: `
<div>
<h2>Price Calculator</h2>
<div class="form-group">
<label>Price per item:</label>
<input v-model.number="price" type="number" min="0">
</div>
<div class="form-group">
<label>Quantity:</label>
<input v-model.number="quantity" type="number" min="1">
</div>
<div class="form-group">
<label>Discount Code:</label>
<input v-model="discountCode" placeholder="Enter code">
<div class="discount-buttons">
<button @click="applyDiscount('SAVE10')">SAVE10</button>
<button @click="applyDiscount('SAVE20')">SAVE20</button>
<button @click="applyDiscount('SAVE30')">SAVE30</button>
</div>
</div>
<div class="breakdown">
<h3>Price Breakdown:</h3>
<p>Subtotal: ${{ subtotal.toFixed(2) }}</p>
<p v-if="discount > 0">Discount: ${{ discount.toFixed(2) }}</p>
<p>Tax (${{ (taxRate * 100).toFixed(0) }}%): ${{ tax.toFixed(2) }}</p>
<hr>
<p><strong>Total: ${{ total.toFixed(2) }}</strong></p>
</div>
</div>
`
};
// 6. Component with event handling and slots
const Card = {
props: {
title: {
type: String,
required: true
},
variant: {
type: String,
default: 'default',
validator: (value) => ['default', 'primary', 'secondary', 'success'].includes(value)
}
},
computed: {
cardClass() {
return ['card', `card-${this.variant}`];
}
},
emits: ['close'],
methods: {
closeCard() {
this.$emit('close');
}
},
template: `
<div :class="cardClass">
<div class="card-header">
<h3>{{ title }}</h3>
<button v-if="$slots.close" @click="closeCard" class="close-btn">
<slot name="close">✕</slot>
</button>
</div>
<div class="card-body">
<slot></slot>
</div>
<div v-if="$slots.footer" class="card-footer">
<slot name="footer"></slot>
</div>
</div>
`
};
// 7. Dynamic components
const DynamicComponentExample = {
data() {
return {
currentComponent: 'welcome',
components: {
welcome: 'WelcomeComponent',
profile: 'ProfileComponent',
settings: 'SettingsComponent'
}
};
},
components: {
WelcomeComponent: {
template: '<h2>Welcome to the Dashboard!</h2>'
},
ProfileComponent: {
template: '<h2>User Profile</h2><p>This is your profile page.</p>'
},
SettingsComponent: {
template: '<h2>Settings</h2><p>Configure your preferences here.</p>'
}
},
template: `
<div>
<div class="navigation">
<button
v-for="(component, key) in components"
:key="key"
@click="currentComponent = key"
:class="{ active: currentComponent === key }"
>
{{ key.charAt(0).toUpperCase() + key.slice(1) }}
</button>
</div>
<div class="content">
<component :is="components[currentComponent]" />
</div>
</div>
`
};
// 8. Component with lifecycle hooks
const LifecycleExample = {
data() {
return {
message: 'Component is active',
counter: 0,
intervalId: null
};
},
created() {
console.log('Component created');
this.startCounter();
},
mounted() {
console.log('Component mounted to DOM');
console.log('Element:', this.$el);
},
beforeUpdate() {
console.log('Component is about to update');
},
updated() {
console.log('Component updated');
},
beforeUnmount() {
console.log('Component is about to be unmounted');
this.stopCounter();
},
unmounted() {
console.log('Component unmounted');
},
methods: {
startCounter() {
this.intervalId = setInterval(() => {
this.counter++;
}, 1000);
},
stopCounter() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
},
template: `
<div>
<h3>Lifecycle Hook Example</h3>
<p>{{ message }}</p>
<p>Counter: {{ counter }}</p>
<p>Check the console for lifecycle events!</p>
</div>
`
};
// 9. Component with provide/inject
const ThemeProvider = {
data() {
return {
theme: 'light',
themes: {
light: {
background: '#ffffff',
color: '#000000',
primary: '#007bff'
},
dark: {
background: '#1a1a1a',
color: '#ffffff',
primary: '#0056b3'
}
}
};
},
provide() {
return {
theme: this.theme,
themes: this.themes,
toggleTheme: this.toggleTheme
};
},
methods: {
toggleTheme() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
}
},
template: `
<div class="theme-provider">
<slot></slot>
</div>
`
};
const ThemedComponent = {
inject: ['theme', 'themes', 'toggleTheme'],
template: `
<div :style="{
backgroundColor: themes[theme].background,
color: themes[theme].color
}">
<h3>Current Theme: {{ theme }}</h3>
<p>This component uses the injected theme.</p>
<button
@click="toggleTheme"
:style="{ backgroundColor: themes[theme].primary }"
>
Toggle Theme
</button>
</div>
`
};
// 10. Complete application example
const App = {
components: {
Card,
Counter,
UserForm,
TodoList,
PriceCalculator,
DynamicComponentExample,
LifecycleExample,
ThemedComponent
},
data() {
return {
activeSection: 'welcome'
};
},
methods: {
showSection(section) {
this.activeSection = section;
}
},
template: `
<div class="app">
<header>
<h1>Vue.js Examples</h1>
<nav>
<button
v-for="section in ['welcome', 'counter', 'form', 'todos', 'calculator', 'dynamic', 'lifecycle', 'themed']"
:key="section"
@click="showSection(section)"
:class="{ active: activeSection === section }"
>
{{ section.charAt(0).toUpperCase() + section.slice(1) }}
</button>
</nav>
</header>
<main>
<!-- Welcome Section -->
<section v-if="activeSection === 'welcome'">
<Card title="Welcome to Vue.js">
<p>This is a collection of Vue.js examples demonstrating various features.</p>
<template #footer>
<p>Click the navigation buttons to explore different examples.</p>
</template>
</Card>
</section>
<!-- Counter Section -->
<section v-if="activeSection === 'counter'">
<Card title="Counter Component">
<Counter />
</Card>
</section>
<!-- Form Section -->
<section v-if="activeSection === 'form'">
<Card title="User Form">
<UserForm />
</Card>
</section>
<!-- Todo List Section -->
<section v-if="activeSection === 'todos'">
<Card title="Todo List">
<TodoList />
</Card>
</section>
<!-- Price Calculator Section -->
<section v-if="activeSection === 'calculator'">
<Card title="Price Calculator">
<PriceCalculator />
</Card>
</section>
<!-- Dynamic Component Section -->
<section v-if="activeSection === 'dynamic'">
<Card title="Dynamic Components">
<DynamicComponentExample />
</Card>
</section>
<!-- Lifecycle Section -->
<section v-if="activeSection === 'lifecycle'">
<Card title="Lifecycle Hooks">
<LifecycleExample />
</Card>
</section>
<!-- Themed Component Section -->
<section v-if="activeSection === 'themed'">
<ThemeProvider>
<Card title="Theme System">
<ThemedComponent />
</Card>
</ThemeProvider>
</section>
</main>
</div>
`
};
export {
HelloWorld,
Counter,
UserForm,
TodoList,
PriceCalculator,
Card,
DynamicComponentExample,
LifecycleExample,
ThemeProvider,
ThemedComponent,
App
};
💻 Vue.js Composition API javascript
🟡 intermediate
⭐⭐⭐⭐
Advanced Vue.js Composition API examples including ref, reactive, computed, watch, and custom composables
⏱️ 40 min
🏷️ vue, composition api, reactivity, hooks
Prerequisites:
Vue.js basics, JavaScript ES6+, Reactivity concepts
// Vue.js Composition API Examples
import { createApp, ref, reactive, computed, watch, watchEffect, onMounted, onUnmounted, nextTick, provide, inject } from 'vue';
// 1. Basic ref and reactive
const BasicReactivity = {
setup() {
// ref for primitive values
const count = ref(0);
const message = ref('Hello Vue 3!');
// reactive for objects
const user = reactive({
name: 'John Doe',
age: 30,
email: '[email protected]'
});
const increment = () => {
count.value++;
};
const updateUser = () => {
user.age++;
user.email = `john${user.age}@example.com`;
};
return {
count,
message,
user,
increment,
updateUser
};
},
template: `
<div>
<h2>Basic Reactivity</h2>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<h3>User Info</h3>
<p>Name: {{ user.name }}</p>
<p>Age: {{ user.age }}</p>
<p>Email: {{ user.email }}</p>
<button @click="updateUser">Update Age</button>
</div>
`
};
// 2. Computed properties
const ComputedExample = {
setup() {
const price = ref(100);
const quantity = ref(2);
const tax = ref(0.08);
// Read-only computed
const subtotal = computed(() => price.value * quantity.value);
const taxAmount = computed(() => subtotal.value * tax.value);
const total = computed(() => subtotal.value + taxAmount.value);
// Writable computed
const totalWithDiscount = computed({
get: () => total.value,
set: (newValue) => {
const discount = total.value - newValue;
const discountPercentage = discount / total.value;
tax.value = Math.max(0, 0.08 - discountPercentage);
}
});
return {
price,
quantity,
tax,
subtotal,
taxAmount,
total,
totalWithDiscount
};
},
template: `
<div>
<h2>Computed Properties</h2>
<div class="form-group">
<label>Price: ${{ price }}</label>
<input v-model.number="price" type="range" min="0" max="1000">
</div>
<div class="form-group">
<label>Quantity: {{ quantity }}</label>
<input v-model.number="quantity" type="range" min="1" max="10">
</div>
<div class="form-group">
<label>Tax Rate: {{ (tax * 100).toFixed(1) }}%</label>
<input v-model.number="tax" type="range" min="0" max="0.3" step="0.01">
</div>
<div class="breakdown">
<p>Subtotal: ${{ subtotal.toFixed(2) }}</p>
<p>Tax: ${{ taxAmount.toFixed(2) }}</p>
<p><strong>Total: ${{ total.toFixed(2) }}</strong></p>
<h4>Writable Computed Example:</h4>
<p>Desired Total: ${{ totalWithDiscount.toFixed(2) }}</p>
<input
v-model.number="totalWithDiscount"
type="number"
:max="total"
step="10"
>
</div>
</div>
`
};
// 3. Watch and watchEffect
const WatchExample = {
setup() {
const searchQuery = ref('');
const results = ref([]);
const loading = ref(false);
const searchHistory = ref([]);
// Watch specific source
watch(
searchQuery,
async (newQuery, oldQuery) => {
if (newQuery.trim()) {
loading.value = true;
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 500));
// Mock search results
results.value = [
`Result 1 for "${newQuery}"`,
`Result 2 for "${newQuery}"`,
`Result 3 for "${newQuery}"`
];
searchHistory.value.push({
query: newQuery,
timestamp: new Date().toLocaleTimeString()
});
loading.value = false;
} else {
results.value = [];
}
},
{ debounce: 300 }
);
// WatchEffect for reactive dependencies
const searchStats = computed(() => ({
queryLength: searchQuery.value.length,
resultsCount: results.value.length,
historyCount: searchHistory.value.length
}));
return {
searchQuery,
results,
loading,
searchHistory,
searchStats
};
},
template: `
<div>
<h2>Watch and WatchEffect</h2>
<div class="search-box">
<input
v-model="searchQuery"
placeholder="Search for something..."
:disabled="loading"
>
<span v-if="loading">Searching...</span>
</div>
<div v-if="results.length > 0" class="results">
<h3>Results:</h3>
<ul>
<li v-for="result in results" :key="result">{{ result }}</li>
</ul>
</div>
<div class="stats">
<h3>Search Statistics:</h3>
<p>Query length: {{ searchStats.queryLength }}</p>
<p>Results found: {{ searchStats.resultsCount }}</p>
<p>Search history: {{ searchStats.historyCount }}</p>
</div>
<div v-if="searchHistory.length > 0" class="history">
<h3>Search History:</h3>
<ul>
<li v-for="item in searchHistory.slice(-5)" :key="item.timestamp">
{{ item.query }} ({{ item.timestamp }})
</li>
</ul>
</div>
</div>
`
};
// 4. Custom composables
function useCounter(initialValue = 0) {
const count = ref(initialValue);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
const reset = () => {
count.value = initialValue;
};
const setValue = (newValue) => {
count.value = newValue;
};
return {
count: readonly(count),
increment,
decrement,
reset,
setValue
};
}
function useToggle(initialState = false) {
const state = ref(initialState);
const toggle = () => {
state.value = !state.value;
};
const setTrue = () => {
state.value = true;
};
const setFalse = () => {
state.value = false;
};
return {
state: readonly(state),
toggle,
setTrue,
setFalse
};
}
function useLocalStorage(key, initialValue) {
const storedValue = ref(initialValue);
// Load from localStorage on mount
if (typeof window !== 'undefined') {
const item = localStorage.getItem(key);
if (item) {
try {
storedValue.value = JSON.parse(item);
} catch (e) {
console.error('Error parsing localStorage item:', e);
}
}
// Watch for changes and save to localStorage
watch(
storedValue,
(newValue) => {
localStorage.setItem(key, JSON.stringify(newValue));
},
{ deep: true }
);
}
const setValue = (value) => {
storedValue.value = value;
};
const removeValue = () => {
if (typeof window !== 'undefined') {
localStorage.removeItem(key);
}
storedValue.value = initialValue;
};
return {
value: storedValue,
setValue,
removeValue
};
}
const ComposablesExample = {
setup() {
// Use counter composable
const { count: counter1, increment: inc1, decrement: dec1, reset: reset1 } = useCounter(10);
const { count: counter2, increment: inc2, decrement: dec2, reset: reset2 } = useCounter(0);
// Use toggle composable
const { state: isDarkMode, toggle: toggleTheme } = useToggle(false);
const { state: isVisible, toggle: toggleVisibility } = useToggle(true);
// Use localStorage composable
const { value: userName, setValue: setUserName } = useLocalStorage('userName', 'Guest');
const { value: preferences, setValue: setPreferences } = useLocalStorage('preferences', {
notifications: true,
theme: 'light'
});
return {
counter1,
inc1,
dec1,
reset1,
counter2,
inc2,
dec2,
reset2,
isDarkMode,
toggleTheme,
isVisible,
toggleVisibility,
userName,
setUserName,
preferences,
setPreferences
};
},
template: `
<div :class="{ 'dark-mode': isDarkMode }">
<h2>Custom Composables</h2>
<!-- Counter Examples -->
<div class="section">
<h3>Counters</h3>
<div class="counter-group">
<div>
<p>Counter 1: {{ counter1 }}</p>
<button @click="inc1">+</button>
<button @click="dec1">-</button>
<button @click="reset1">Reset</button>
</div>
<div>
<p>Counter 2: {{ counter2 }}</p>
<button @click="inc2">+</button>
<button @click="dec2">-</button>
<button @click="reset2">Reset</button>
</div>
</div>
</div>
<!-- Toggle Examples -->
<div class="section">
<h3>Toggles</h3>
<p>Dark Mode: {{ isDarkMode ? 'ON' : 'OFF' }}</p>
<button @click="toggleTheme">Toggle Dark Mode</button>
<p>Content Visible: {{ isVisible ? 'YES' : 'NO' }}</p>
<button @click="toggleVisibility">Toggle Visibility</button>
<div v-if="isVisible" class="toggle-content">
This content can be toggled on and off!
</div>
</div>
<!-- LocalStorage Examples -->
<div class="section">
<h3>LocalStorage</h3>
<div>
<label>User Name:</label>
<input v-model="userName" @input="setUserName($event.target.value)">
<p>Persisted: {{ userName }}</p>
</div>
<div>
<label>Notifications:</label>
<input
type="checkbox"
v-model="preferences.notifications"
@change="setPreferences(preferences)"
>
<label>Theme:</label>
<select
v-model="preferences.theme"
@change="setPreferences(preferences)"
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<p>Preferences: {{ JSON.stringify(preferences) }}</p>
</div>
</div>
</div>
`
};
// 5. Lifecycle hooks in Composition API
const LifecycleComposableExample = {
setup() {
const data = ref('Initial data');
const mountedTime = ref(null);
const updateCount = ref(0);
let intervalId = null;
onMounted(() => {
console.log('Component mounted');
mountedTime.value = new Date().toLocaleTimeString();
// Start a timer
intervalId = setInterval(() => {
updateCount.value++;
}, 1000);
});
onUnmounted(() => {
console.log('Component unmounted');
if (intervalId) {
clearInterval(intervalId);
}
});
const updateData = async () => {
data.value = 'Updating...';
await nextTick(); // Wait for DOM update
data.value = `Updated at ${new Date().toLocaleTimeString()}`;
};
return {
data,
mountedTime,
updateCount,
updateData
};
},
template: `
<div>
<h2>Lifecycle Hooks</h2>
<p>Mounted at: {{ mountedTime }}</p>
<p>Update count: {{ updateCount }}</p>
<p>Data: {{ data }}</p>
<button @click="updateData">Update Data</button>
<p>Check console for lifecycle events</p>
</div>
`
};
// 6. Provide and Inject
const ThemeProviderComposable = {
setup() {
const theme = ref('light');
const themes = reactive({
light: {
primary: '#007bff',
background: '#ffffff',
color: '#000000'
},
dark: {
primary: '#0056b3',
background: '#1a1a1a',
color: '#ffffff'
}
});
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light';
};
provide('theme', theme);
provide('themes', themes);
provide('toggleTheme', toggleTheme);
return {
theme,
toggleTheme
};
},
template: `
<div class="theme-provider">
<slot></slot>
</div>
`
};
const ThemedComponentComposable = {
setup() {
const theme = inject('theme');
const themes = inject('themes');
const toggleTheme = inject('toggleTheme');
return {
theme,
themes,
toggleTheme
};
},
template: `
<div
:style="{
backgroundColor: themes[theme].background,
color: themes[theme].color,
padding: '20px',
borderRadius: '8px'
}"
>
<h3>Themed Component</h3>
<p>Current theme: {{ theme }}</p>
<button
@click="toggleTheme"
:style="{
backgroundColor: themes[theme].primary,
color: themes[theme].background,
border: 'none',
padding: '8px 16px',
borderRadius: '4px'
}"
>
Toggle Theme
</button>
</div>
`
};
// 7. Teleport example
const TeleportExample = {
setup() {
const showModal = ref(false);
const openModal = () => {
showModal.value = true;
};
const closeModal = () => {
showModal.value = false;
};
return {
showModal,
openModal,
closeModal
};
},
template: `
<div>
<h2>Teleport Example</h2>
<button @click="openModal">Open Modal</button>
<Teleport to="body">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<h3>Teleported Modal</h3>
<p>This modal is rendered at the document body level.</p>
<button @click="closeModal">Close</button>
</div>
</div>
</Teleport>
</div>
`
};
export {
BasicReactivity,
ComputedExample,
WatchExample,
ComposablesExample,
LifecycleComposableExample,
ThemeProviderComposable,
ThemedComponentComposable,
TeleportExample,
useCounter,
useToggle,
useLocalStorage
};
💻 Vue.js Router and Pinia javascript
🔴 complex
⭐⭐⭐⭐⭐
Vue.js application patterns with Vue Router for navigation and Pinia for state management
⏱️ 50 min
🏷️ vue, router, pinia, spa, state
Prerequisites:
Vue.js Composition API, State management concepts, Routing concepts
// Vue.js Router and Pinia Examples
import { createRouter, createWebHistory } from 'vue-router';
import { createPinia, defineStore } from 'pinia';
import { ref, computed, watch } from 'vue';
// 1. Pinia Store Examples
// User Store
export const useUserStore = defineStore('user', () => {
// State
const user = ref(null);
const isLoggedIn = computed(() => !!user.value);
const userRole = computed(() => user.value?.role || 'guest');
// Actions
const login = async (credentials) => {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
user.value = {
id: 1,
name: credentials.name,
email: credentials.email,
role: 'user',
token: 'mock-jwt-token'
};
return user.value;
};
const logout = () => {
user.value = null;
};
const updateProfile = (profileData) => {
if (user.value) {
user.value = { ...user.value, ...profileData };
}
};
return {
user,
isLoggedIn,
userRole,
login,
logout,
updateProfile
};
});
// Product Store
export const useProductStore = defineStore('products', () => {
// State
const products = ref([]);
const loading = ref(false);
const error = ref(null);
const searchQuery = ref('');
const selectedCategory = ref('all');
// Getters
const filteredProducts = computed(() => {
let filtered = products.value;
// Filter by search query
if (searchQuery.value) {
filtered = filtered.filter(product =>
product.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
product.description.toLowerCase().includes(searchQuery.value.toLowerCase())
);
}
// Filter by category
if (selectedCategory.value !== 'all') {
filtered = filtered.filter(product => product.category === selectedCategory.value);
}
return filtered;
});
const categories = computed(() => {
const cats = new Set(products.value.map(p => p.category));
return ['all', ...Array.from(cats)];
});
const productById = (id) => {
return computed(() => products.value.find(p => p.id === parseInt(id)));
};
// Actions
const fetchProducts = async () => {
loading.value = true;
error.value = null;
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1500));
products.value = [
{ id: 1, name: 'Laptop', price: 999, category: 'electronics', description: 'Powerful laptop for work and gaming', stock: 10 },
{ id: 2, name: 'Smartphone', price: 699, category: 'electronics', description: 'Latest smartphone with great camera', stock: 25 },
{ id: 3, name: 'Headphones', price: 199, category: 'electronics', description: 'Noise-cancelling wireless headphones', stock: 15 },
{ id: 4, name: 'Coffee Maker', price: 149, category: 'home', description: 'Automatic coffee maker', stock: 8 },
{ id: 5, name: 'Desk Chair', price: 299, category: 'furniture', description: 'Ergonomic office chair', stock: 5 },
{ id: 6, name: 'Bookshelf', price: 199, category: 'furniture', description: 'Wooden bookshelf', stock: 3 }
];
} catch (err) {
error.value = 'Failed to fetch products';
} finally {
loading.value = false;
}
};
const addProduct = (product) => {
const newProduct = {
...product,
id: Math.max(...products.value.map(p => p.id)) + 1
};
products.value.push(newProduct);
};
const updateProduct = (id, updates) => {
const index = products.value.findIndex(p => p.id === parseInt(id));
if (index !== -1) {
products.value[index] = { ...products.value[index], ...updates };
}
};
const deleteProduct = (id) => {
products.value = products.value.filter(p => p.id !== parseInt(id));
};
return {
products,
loading,
error,
searchQuery,
selectedCategory,
filteredProducts,
categories,
productById,
fetchProducts,
addProduct,
updateProduct,
deleteProduct
};
});
// Cart Store
export const useCartStore = defineStore('cart', () => {
// State
const items = ref([]);
const total = computed(() => {
return items.value.reduce((sum, item) => sum + (item.price * item.quantity), 0);
});
const itemCount = computed(() => {
return items.value.reduce((sum, item) => sum + item.quantity, 0);
});
// Actions
const addToCart = (product, quantity = 1) => {
const existingItem = items.value.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
items.value.push({
id: product.id,
name: product.name,
price: product.price,
quantity
});
}
};
const removeFromCart = (productId) => {
items.value = items.value.filter(item => item.id !== productId);
};
const updateQuantity = (productId, quantity) => {
const item = items.value.find(item => item.id === productId);
if (item) {
if (quantity <= 0) {
removeFromCart(productId);
} else {
item.quantity = quantity;
}
}
};
const clearCart = () => {
items.value = [];
};
return {
items,
total,
itemCount,
addToCart,
removeFromCart,
updateQuantity,
clearCart
};
});
// 2. Vue Router Configuration
const routes = [
{
path: '/',
name: 'Home',
component: {
template: `
<div class="home">
<h1>Welcome to Vue Shop</h1>
<p>Browse our products or check your cart</p>
</div>
`
}
},
{
path: '/products',
name: 'Products',
component: {
setup() {
const productStore = useProductStore();
const cartStore = useCartStore();
onMounted(() => {
productStore.fetchProducts();
});
return {
productStore,
cartStore
};
},
template: `
<div class="products">
<h1>Products</h1>
<div class="filters">
<input
v-model="productStore.searchQuery"
placeholder="Search products..."
>
<select v-model="productStore.selectedCategory">
<option v-for="category in productStore.categories" :key="category" :value="category">
{{ category.charAt(0).toUpperCase() + category.slice(1) }}
</option>
</select>
</div>
<div v-if="productStore.loading" class="loading">
Loading products...
</div>
<div v-else-if="productStore.error" class="error">
{{ productStore.error }}
</div>
<div v-else class="product-grid">
<div v-for="product in productStore.filteredProducts" :key="product.id" class="product-card">
<h3>{{ product.name }}</h3>
<p>{{ product.description }}</p>
<p class="price">${{ product.price }}</p>
<p class="category">{{ product.category }}</p>
<p class="stock">Stock: {{ product.stock }}</p>
<button
@click="cartStore.addToCart(product)"
:disabled="product.stock === 0"
>
Add to Cart
</button>
</div>
</div>
</div>
`
}
},
{
path: '/product/:id',
name: 'ProductDetail',
component: {
setup() {
const route = useRoute();
const productStore = useProductStore();
const cartStore = useCartStore();
const product = computed(() => productStore.productById(route.params.id));
onMounted(() => {
if (productStore.products.length === 0) {
productStore.fetchProducts();
}
});
return {
product,
cartStore
};
},
template: `
<div v-if="product" class="product-detail">
<h1>{{ product.name }}</h1>
<p class="price">${{ product.price }}</p>
<p>{{ product.description }}</p>
<p>Category: {{ product.category }}</p>
<p>Stock: {{ product.stock }}</p>
<button
@click="cartStore.addToCart(product)"
:disabled="product.stock === 0"
>
Add to Cart
</button>
</div>
<div v-else>
Product not found
</div>
`
}
},
{
path: '/cart',
name: 'Cart',
component: {
setup() {
const cartStore = useCartStore();
return { cartStore };
},
template: `
<div class="cart">
<h1>Shopping Cart</h1>
<div v-if="cartStore.items.length === 0" class="empty-cart">
Your cart is empty
</div>
<div v-else>
<div class="cart-items">
<div v-for="item in cartStore.items" :key="item.id" class="cart-item">
<h3>{{ item.name }}</h3>
<p>${{ item.price }} each</p>
<div class="quantity-controls">
<button @click="cartStore.updateQuantity(item.id, item.quantity - 1)">-</button>
<span>{{ item.quantity }}</span>
<button @click="cartStore.updateQuantity(item.id, item.quantity + 1)">+</button>
</div>
<button @click="cartStore.removeFromCart(item.id)" class="remove">Remove</button>
</div>
</div>
<div class="cart-summary">
<p>Total Items: {{ cartStore.itemCount }}</p>
<p>Total: ${{ cartStore.total.toFixed(2) }}</p>
<button @click="cartStore.clearCart">Clear Cart</button>
<button class="checkout">Checkout</button>
</div>
</div>
</div>
`
}
},
{
path: '/profile',
name: 'Profile',
component: {
setup() {
const userStore = useUserStore();
const editing = ref(false);
const form = ref({});
const startEdit = () => {
form.value = { ...userStore.user };
editing.value = true;
};
const saveProfile = () => {
userStore.updateProfile(form.value);
editing.value = false;
};
const cancelEdit = () => {
editing.value = false;
};
return {
userStore,
editing,
form,
startEdit,
saveProfile,
cancelEdit
};
},
template: `
<div class="profile">
<h1>User Profile</h1>
<div v-if="!userStore.isLoggedIn">
<p>Please log in to view your profile</p>
</div>
<div v-else>
<div v-if="!editing">
<h2>{{ userStore.user.name }}</h2>
<p>Email: {{ userStore.user.email }}</p>
<p>Role: {{ userStore.userRole }}</p>
<button @click="startEdit">Edit Profile</button>
<button @click="userStore.logout">Logout</button>
</div>
<div v-else>
<input v-model="form.name" placeholder="Name">
<input v-model="form.email" placeholder="Email">
<button @click="saveProfile">Save</button>
<button @click="cancelEdit">Cancel</button>
</div>
</div>
</div>
`
}
},
{
path: '/login',
name: 'Login',
component: {
setup() {
const userStore = useUserStore();
const router = useRouter();
const credentials = ref({ name: '', email: '' });
const loading = ref(false);
const handleLogin = async () => {
loading.value = true;
try {
await userStore.login(credentials.value);
router.push('/');
} catch (error) {
console.error('Login failed:', error);
} finally {
loading.value = false;
}
};
return {
credentials,
loading,
handleLogin
};
},
template: `
<div class="login">
<h1>Login</h1>
<form @submit.prevent="handleLogin">
<input v-model="credentials.name" placeholder="Name" required>
<input v-model="credentials.email" type="email" placeholder="Email" required>
<button type="submit" :disabled="loading">
{{ loading ? 'Logging in...' : 'Login' }}
</button>
</form>
</div>
`
}
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
// 3. Navigation Guards
router.beforeEach((to, from, next) => {
const userStore = useUserStore();
// Redirect to login if trying to access profile without being logged in
if (to.name === 'Profile' && !userStore.isLoggedIn) {
next({ name: 'Login' });
} else {
next();
}
});
// 4. Main App Component
const App = {
setup() {
const userStore = useUserStore();
const cartStore = useCartStore();
const router = useRouter();
const route = useRoute();
const navigation = [
{ name: 'Home', path: '/' },
{ name: 'Products', path: '/products' },
{ name: 'Cart', path: '/cart' },
{ name: 'Profile', path: '/profile' }
];
return {
userStore,
cartStore,
router,
route,
navigation
};
},
template: `
<div class="app">
<header>
<nav>
<router-link
v-for="item in navigation"
:key="item.path"
:to="item.path"
:class="{ active: route.path === item.path }"
>
{{ item.name }}
</router-link>
</nav>
<div class="header-actions">
<router-link to="/cart" class="cart-link">
Cart ({{ cartStore.itemCount }})
</router-link>
<span v-if="userStore.isLoggedIn">
{{ userStore.user.name }}
</span>
<router-link v-else to="/login">
Login
</router-link>
</div>
</header>
<main>
<router-view />
</main>
<footer>
<p>© 2024 Vue Shop Demo</p>
</footer>
</div>
`
};
export {
useUserStore,
useProductStore,
useCartStore,
router,
App
};