Vue.js Samples
Vue.js component examples including Composition API, Options API, reactive state, and modern Vue patterns
Key Facts
- Category
- Web Frameworks
- Items
- 3
- Format Families
- sample
Sample Overview
Vue.js component examples including Composition API, Options API, reactive state, and modern Vue patterns This sample set belongs to Web Frameworks and can be used to test related workflows inside Elysia Tools.
💻 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, ref } 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)
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
};