Frontend Setup and Configuration
Vue.js Project Initialization
npm init vue@latest mycloud-notes
cd mycloud-notes
npm install
Essential Dependencies Installation
npm install element-plus axios sass vue-router@4 pinia pinia-persistedstate-plugin
Main Configuration File
// main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import { createPersistedState } from 'pinia-persistedstate-plugin';
import ElementPlus from 'element-plus';
import locale from 'element-plus/dist/locale/zh-cn.js';
import 'element-plus/dist/index.css';
import router from '@/router';
import App from './App.vue';
const app = createApp(App);
const pinia = createPinia();
const persist = createPersistedState();
pinia.use(persist);
app.use(pinia);
app.use(ElementPlus, { locale });
app.use(router);
app.mount('#app');
Authentication System Implementation
Login Component Structure
<template>
<el-row class="auth-container">
<el-col class="background-section">
<el-row class="brand-logo"></el-row>
<el-row class="auth-form">
<el-form v-if="isRegistrationMode" :model="authData" :rules="validationRules">
<el-form-item>
<h1>Create Account</h1>
</el-form-item>
<el-form-item prop="username">
<el-input v-model="authData.username" placeholder="Enter username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="authData.password" type="password" placeholder="Enter password"></el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input v-model="authData.confirmPassword" type="password" placeholder="Confirm password"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="handleRegistration">Register</el-button>
</el-form-item>
</el-form>
<el-form v-else :model="authData" :rules="loginRules">
<el-form-item>
<h1>Sign In</h1>
</el-form-item>
<el-form-item prop="username">
<el-input v-model="authData.username" placeholder="Enter username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="authData.password" type="password" placeholder="Enter password"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="handleLogin">Login</el-button>
</el-form-item>
</el-form>
</el-row>
</el-col>
</el-row>
</template>
<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useTokenStore } from '@/stores/auth';
import { registerUser, authenticateUser } from '@/api/auth';
const router = useRouter();
const tokenStore = useTokenStore();
const isRegistrationMode = ref(false);
const authData = ref({
username: '',
password: '',
confirmPassword: ''
});
const validationRules = {
username: [
{ required: true, message: 'Username required', trigger: 'blur' },
{ min: 5, max: 16, message: '5-16 characters required', trigger: 'blur' }
],
password: [
{ required: true, message: 'Password required', trigger: 'blur' },
{ min: 5, max: 16, message: '5-16 characters required', trigger: 'blur' }
],
confirmPassword: [
{ validator: validatePasswordMatch, trigger: 'blur' }
]
};
function validatePasswordMatch(rule, value, callback) {
if (value !== authData.value.password) {
callback(new Error('Passwords must match'));
} else {
callback();
}
}
async function handleRegistration() {
await registerUser(authData.value);
isRegistrationMode.value = false;
}
async function handleLogin() {
const response = await authenticateUser(authData.value);
tokenStore.setAuthToken(response.data);
router.push('/');
}
</script>
API Service Implementation
// api/auth.js
import request from '@/utils/request';
export const registerUser = (userData) => {
const params = new URLSearchParams();
Object.keys(userData).forEach(key => {
params.append(key, userData[key]);
});
return request.post('/user/register', params);
};
export const authenticateUser = (credentials) => {
const params = new URLSearchParams();
Object.keys(credentials).forEach(key => {
params.append(key, credentials[key]);
});
return request.post('/user/login', params);
};
State Managmeent with Pinia
// stores/auth.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useTokenStore = defineStore('auth', () => {
const authToken = ref('');
const setAuthToken = (token) => {
authToken.value = token;
};
const removeAuthToken = () => {
authToken.value = '';
};
return { authToken, setAuthToken, removeAuthToken };
}, {
persist: true
});
Application Routing Configuration
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import LoginView from '@/views/Login.vue';
import MainLayout from '@/views/MainLayout.vue';
import ArticleCategories from '@/views/articles/Categories.vue';
import ArticleManagement from '@/views/articles/Management.vue';
import UserProfile from '@/views/user/Profile.vue';
const routes = [
{ path: '/login', component: LoginView },
{
path: '/',
component: MainLayout,
redirect: '/articles',
children: [
{ path: '/articles/categories', component: ArticleCategories },
{ path: '/articles', component: ArticleManagement },
{ path: '/user/profile', component: UserProfile }
]
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
HTTP Request Configuration
// utils/request.js
import axios from 'axios';
import { ElMessage } from 'element-plus';
import router from '@/router';
import { useTokenStore } from '@/stores/auth';
const apiClient = axios.create({
baseURL: '/api'
});
apiClient.interceptors.request.use(
(config) => {
const tokenStore = useTokenStore();
if (tokenStore.authToken) {
config.headers.Authorization = tokenStore.authToken;
}
return config;
},
(error) => Promise.reject(error)
);
apiClient.interceptors.response.use(
(response) => {
if (response.data.code === 0) {
return response.data;
}
ElMessage.error(response.data.message || 'Operation failed');
return Promise.reject(response.data);
},
(error) => {
if (error.response?.status === 401) {
ElMessage.error('Authentication required');
router.push('/login');
} else {
ElMessage.error('Service unavailable');
}
return Promise.reject(error);
}
);
export default apiClient;
Article Management Features
Category Management Component
<template>
<el-card class="management-panel">
<template #header>
<div class="panel-header">
<span>Article Categories</span>
<el-button @click="showCategoryDialog">Add Category</el-button>
</div>
</template>
<el-table :data="categories">
<el-table-column label="ID" type="index" width="80" />
<el-table-column label="Name" prop="categoryName" />
<el-table-column label="Alias" prop="categoryAlias" />
<el-table-column label="Actions" width="120">
<template #default="{ row }">
<el-button icon="Edit" @click="editCategory(row)" />
<el-button icon="Delete" @click="removeCategory(row)" />
</template>
</el-table-column>
</el-table>
<el-dialog :title="dialogTitle" v-model="dialogVisible">
<el-form :model="currentCategory" :rules="categoryRules">
<el-form-item label="Category Name" prop="categoryName">
<el-input v-model="currentCategory.categoryName" />
</el-form-item>
<el-form-item label="Category Alias" prop="categoryAlias">
<el-input v-model="currentCategory.categoryAlias" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button @click="saveCategory">Confirm</el-button>
</template>
</el-dialog>
</el-card>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import {
fetchCategories,
createCategory,
updateCategory,
deleteCategory
} from '@/api/articles';
const categories = ref([]);
const dialogVisible = ref(false);
const dialogTitle = ref('');
const currentCategory = ref({ id: null, categoryName: '', categoryAlias: '' });
const categoryRules = {
categoryName: [{ required: true, message: 'Name required' }],
categoryAlias: [{ required: true, message: 'Alias required' }]
};
onMounted(async () => {
await loadCategories();
});
async function loadCategories() {
const response = await fetchCategories();
categories.value = response.data;
}
function showCategoryDialog() {
dialogTitle.value = 'Add Category';
currentCategory.value = { id: null, categoryName: '', categoryAlias: '' };
dialogVisible.value = true;
}
function editCategory(category) {
dialogTitle.value = 'Edit Category';
currentCategory.value = { ...category };
dialogVisible.value = true;
}
async function saveCategory() {
if (currentCategory.value.id) {
await updateCategory(currentCategory.value);
} else {
await createCategory(currentCategory.value);
}
dialogVisible.value = false;
await loadCategories();
}
async function removeCategory(category) {
try {
await ElMessageBox.confirm('Confirm deletion?', 'Warning', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'warning'
});
await deleteCategory(category.id);
await loadCategories();
} catch {
ElMessage.info('Deletion cancelled');
}
}
</script>
Article API Services
// api/articles.js
import request from '@/utils/request';
export const fetchCategories = () => request.get('/category');
export const createCategory = (categoryData) =>
request.post('/category', categoryData);
export const updateCategory = (categoryData) =>
request.put('/category', categoryData);
export const deleteCategory = (id) =>
request.delete(`/category?id=${id}`);
export const fetchArticles = (params) =>
request.get('/article', { params });
export const createArticle = (articleData) =>
request.post('/article', articleData);
User Profile Management
Profile Update Component
<template>
<el-card class="profile-panel">
<template #header>
<div class="panel-header">
<span>User Profile</span>
</div>
</template>
<el-form :model="userProfile" :rules="profileRules" label-width="100px">
<el-form-item label="Username">
<el-input v-model="userProfile.username" disabled />
</el-form-item>
<el-form-item label="Display Name" prop="nickname">
<el-input v-model="userProfile.nickname" />
</el-form-item>
<el-form-item label="Email" prop="email">
<el-input v-model="userProfile.email" />
</el-form-item>
<el-form-item>
<el-button @click="updateProfile">Save Changes</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { useUserStore } from '@/stores/user';
import { fetchUserProfile, updateUserProfile } from '@/api/user';
const userStore = useUserStore();
const userProfile = ref({
username: '',
nickname: '',
email: ''
});
const profileRules = {
nickname: [
{ required: true, message: 'Display name required' },
{ pattern: /^\S{2,10}$/, message: '2-10 non-space characters' }
],
email: [
{ required: true, message: 'Email required' },
{ type: 'email', message: 'Valid email required' }
]
};
onMounted(async () => {
await loadUserProfile();
});
async function loadUserProfile() {
const response = await fetchUserProfile();
userProfile.value = response.data;
userStore.setUserInfo(response.data);
}
async function updateProfile() {
await updateUserProfile(userProfile.value);
userStore.setUserInfo(userProfile.value);
ElMessage.success('Profile updated');
}
</script>
User Store Implementation
// stores/user.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useUserStore = defineStore('user', () => {
const userInfo = ref({});
const setUserInfo = (info) => {
userInfo.value = info;
};
const clearUserInfo = () => {
userInfo.value = {};
};
return { userInfo, setUserInfo, clearUserInfo };
}, {
persist: true
});