Cấu trúc dự án Vue cơ bản
Quy trình phát triển module frontend thường bao gồm:
- Xây dựng giao diện tĩnh và chia thành các thành phần độc lập
- Thực hiện các yêu cầu API
- Quản lý trạng thái bằng Vuex (actions, mutations, state)
- Hiển thị dữ liệu động từ kho lưu trữ
Cấu trúc thư mục
Thư mục public: Chứa tài nguyên tĩnh, được webpack giữ nguyên khi đóng gói vào thư mục dist.
public/index.html: File template tạo ra trang chủ, tự động chèn các file JS/CSS đã đóng gói. Trình duyệt sẽ hiển thị file này khi truy cập dự án.
Thư mục src (mã nguồn):
assets: Tài nguyên chungcomponents: Thành phần không thuộc route (toàn cục)App.vue: Thành phần gốc duy nhấtmain.js: Điểm vào đầu tiên của ứng dụng
babel.config.js: Cấu hình Babel
package.json: Thông tin chi tiết dự án
package-lock.json: File lưu trữ phiên bản các gói
Cấu hình dự án
Khởi động tự động trình duyệt
Trong package.json:
"scripts": {
"start": "vue-cli-service serve --open --host 0.0.0.0",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
}
Tắt ESLint
Tạo file vue.config.js tại gốc dự án:
module.exports = {
lintOnSave: false,
configureWebpack: {
devtool: 'source-map',
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
}
Cấu hình đường dẫn alias
Tạo jsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}
Xử lý样式
Để sử dụng LESS cho các thành phần:
npm install --save less less-loader@5
Khai báo trong component:
<script lang="less" scoped>
Xóa样式 mặc định
Thêm vào public/index.html:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
Cấu hình router
Tạo thư mục router với file index.js:
import Vue from 'vue';
import Router from 'vue-router';
import Home from '@/pages/Home';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
}
]
});
Đăng ký trong main.js:
import router from './router';
new Vue({
router,
render: h => h(App)
}).$mount('#app');
Ẩn/hiện footer
Sử dụng v-show thay vì v-if để tránh thao tác DOM thường xuyên. Cấu hình meta trong route:
{
path: '/login',
component: Login,
meta: { showFooter: false }
}
Sử dụng trong component:
<Footer v-show="$route.meta.showFooter" />
Chuyển hướng route
Query và params
query: Tham số trong URL dạng /search?k1=v1
params: Tham số trong đường dẫn /search/:keyword
Xử lý params tùy chọn:
path: "/search/:keyword?"
Xử lý params rỗng:
this.$router.push({ name: "Search", params: { keyword: '' || undefined } })
Chuyển hướng bằng props
// Cách 3: Sử dụng hàm trả về props
props(route) {
return {
id: route.query.id,
title: route.query.title
}
}
Giải quyết cảnh báo push lặp lại
Viết lại phương thức push trong router:
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function(location, resolve, reject) {
if (resolve && reject) {
originalPush.call(this, location, resolve, reject);
} else {
originalPush.call(this, location, () => {}, () => {});
}
};
封装 Axios
Tạo file api/request.js:
import axios from 'axios';
const apiService = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL,
timeout: 10000
});
apiService.interceptors.request.use(config => {
const token = localStorage.getItem('user_token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
});
apiService.interceptors.response.use(response => {
return response.data;
}, error => {
console.error('Lỗi phản hồi:', error);
return Promise.reject(new Error('Lỗi kết nối'));
});
export default apiService;
Giải quyết vấn đề CORS
Trong vue.config.js:
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://39.98.123.211',
changeOrigin: true
}
}
}
};
Xử lý dữ liệu Vuex
Trong store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
bannerList: []
},
mutations: {
SET_BANNER_LIST(state, data) {
state.bannerList = data;
}
},
actions: {
async fetchBannerList({ commit }) {
const result = await apiService.get('/banner');
commit('SET_BANNER_LIST', result);
}
}
});
Swiper cho slider
Sử dụng this.$nextTick() để đảm bảo DOM đã render:
watch: {
bannerList: {
immediate: true,
handler() {
this.$nextTick(() => {
new Swiper(this.$refs.slider, {
loop: true,
pagination: { el: '.swiper-pagination' },
navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' }
});
});
}
}
}
Thiết kế component
Component cha truyền dữ liệu cho con qua props:
<!-- Component cha -->
<Floor :data="floorItem" />
<!-- Component con -->
<script>
export default {
props: ['data']
}
</script>
Xử lý dữ liệu động
Sử dụng watch theo dõi thay đổi route:
watch: {
$route: {
handler(newVal) {
Object.assign(this.params, newVal.query, newVal.params);
this.fetchData();
}
}
}
Thực hiện phân trang
Tính toán trang hiển thị:
computed: {
pageRange() {
const start = Math.max(1, this.currentPage - 2);
const end = Math.min(this.totalPages, start + 4);
return { start, end };
}
}
Xử lý sự kiện blur
So sánh giá trị trước và sau khi blur:
methods: {
handleBlur(event) {
if (event.target.value !== this.originalValue) {
this.saveChanges();
}
}
}
Thiết kế thanh điều hướng
Thay đổi trạng thái dựa trên route:
computed: {
isActive() {
return this.$route.path === this.routePath;
}
}
Quản lý trạng thái đăng nhập
Trong router/index.js:
router.beforeEach(async (to, from, next) => {
const token = localStorage.getItem('auth_token');
if (to.path === '/login' && token) {
next('/dashboard');
} else if (!token && to.meta.requiresAuth) {
next('/login');
} else {
next();
}
});
Tối ưu hóa tải
Sử dụng lazy loading cho components:
component: () => import('@/views/Profile')