前言
最近在看前端工程化的内容,在思考如何快速从0开始搭建一个前端项目以及前端项目如何进行代码分层。有一些收获,随手记一下。最近也在写一些demo工程,欢迎大佬来star一下:
基于vue ui研发的飞雪随笔平台前端源码:https://github.com/tdx1997tdx/my_tapd_frontend
vue ui
偶然间发现了vue ui工具,是真的香。直接省略了很多繁琐的配置,将插件,依赖,配置等信息直接可视化出来,特别利于新手研发人员专注业务开发。
直接打开一个vue3项目,可以直接clone上面的地址,输入命令vue ui,就会出现下面页面:
在这边可以直接搜索前端插件并配置,不需要再去webpack中配置了
对于vue的周边依赖,如vue-router,vuex,element-ui-plus等,就可以在这边进行配置
如果要启动项目,就可以在这个页面进行启动或者编译,方便快捷
具体可以去google一下
前端代码分层
为了方便研发与源码阅读,有必要对代码进行分层,在飞雪测试平台项目中,做了如下分层
api层
这个包主要存放与后台交互的接口方法
import http from "../utils/http";
import proxy from "../config/proxy";
const config = {proxy, headers: {"Content-Type": "application/json"}, timeout: 1000000};
// 登录
export const login = (data) => http({url: "/login", method: "post", data, config: config});
//注册
export const regist = (data) => http({url: "/regist", method: "post", data, config: config});
//心跳
export const report = () => http({url: "/report", method: "get", config: config});
assets层
主要存放图片以及其他文件
component层
主要存放页面公用组件,这边以Login.vue为例
<template>
<el-form :model="formData" ref="formData" label-width="0px" class="form_box" :rules="rules">
<el-form-item>
<h1 style="margin: 0 auto; text-align: center">欢迎来到飞雪测试平台</h1>
</el-form-item>
<el-form-item prop="username">
<el-input placeholder="请输入用户名" v-model="formData.username" prefix-icon="el-icon-search"/>
</el-form-item>
<el-form-item prop="password">
<el-input placeholder="请输入密码" v-model="formData.password" prefix-icon="el-icon-lock" show-password/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="formData.isCheck" label="记住密码" size="large"/>
<router-link to="registItem" style="float: right">用户注册</router-link>
</el-form-item>
<el-form-item style="float: right">
<el-button type="primary" @click="submitForm('formData')">登录</el-button>
<el-button type="info" @click="resetForm('formData')">清空</el-button>
</el-form-item>
</el-form>
</template>
<script>
import {login} from "@/api/api";
export default {
name: "LoginView",
data() {
return {
formData: {
username: "",
password: "",
isCheck: false
},
rules: {
username: [
{required: true, message: "请输入用户名", trigger: "blur"}
],
password: [
{required: true, message: "请输入密码", trigger: "blur"}
]
}
};
},
methods: {
submitForm: function (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
login({
username: this.formData.username,
password: this.formData.password
}).then(res => {
if (res.code === 0) {
this.$message.success("登录成功");
localStorage.setItem("token", res.data.token);
localStorage.setItem("loginName", this.formData.username);
if (this.formData.isCheck) {
localStorage.setItem("password", this.formData.password);
} else {
localStorage.setItem("password", "");
}
this.$router.push("/home");
} else {
this.$message.warning(res.msg);
}
});
} else {
return false;
}
}).catch(error => {
this.$message.warning("服务器错误" + error);
});
},
resetForm: function (formName) {
this.$refs[formName].resetFields();
this.formData.password = "";
this.formData.username = "";
},
initForm: function () {
let username = localStorage.getItem("loginName");
let password = localStorage.getItem("password");
if (username) {
this.formData.username = username;
}
if (password) {
this.formData.password = password;
this.formData.isCheck = true;
}
}
},
components: {},
mounted() {
this.initForm();
}
};
</script>
<style scoped>
.form_box {
margin: 30px;
}
</style>
config层
主要存放配置信息,比如环境分离
export default {
development: {
// 开发环境接口请求
host: "http://localhost:5000/api/"
},
production: {
// 正式环境接口地址
host: "http://139.198.36.243/api/"
}
};
router层
主要存放前端路由信息
import {createRouter, createWebHashHistory} from "vue-router";
import HomeView from "../views/HomeView.vue";
import LoginView from "../views/LoginView.vue";
import Regist from "../components/Regist.vue";
import Login from "../components/Login.vue";
const routes = [
{
path: "/",
redirect: "/home"
},
{
path: "/home",
name: "home",
component: HomeView
},
{
path: "/login",
name: "login",
component: LoginView,
redirect: "/login/loginItem",
children: [
{
title: "登录组件",
path: "loginItem",
component: Login
},
{
title: "注册组件",
path: "registItem",
component: Regist
}
]
}
];
const router = createRouter({
history: createWebHashHistory(),
routes
});
export default router;
store层
存放vuex全局变量
import { createStore } from 'vuex'
export default createStore({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
utils层
存放通用工具,如请求通用工具,封装axios等
/** ** http.js ****/
// 导入封装好的axios实例
import axios from "axios";
const http = ({method, url, data, config, headers = {"Content-Type": "application/json;charset=UTF-8;"}}) => {
const env = process.env.NODE_ENV || "development";
const {host} = config.proxy[env];
const baseURL = host;
const instance = axios.create({
baseURL, // 接口统一域名
timeout: 6000, // 设置超时
headers: headers
});
// 请求拦截器
instance.interceptors.request.use(
(config) => {
// 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加 了
const token = window.localStorage.getItem("token");
const loginName = localStorage.getItem("loginName");
token && (config.headers.Authorization = token);
loginName && (config.headers.loginName = loginName);
// 若请求方式为post,则将data参数转为JSON字符串
if (config.method === "post" || config.method === "put") {
config.data = JSON.stringify(config.data);
}
return config;
},
(error) =>
// 对请求错误做些什么
Promise.reject(error)
);
// 响应拦截器
instance.interceptors.response.use(
(response) =>
// hideLoading();
// 响应成功
// console.log('响应成功');
response.data,
(error) => {
console.log(error);
// 响应错误
if (error.response && error.response.status) {
return Promise.reject(error);
}
return Promise.reject(error);
}
);
method = method.toLowerCase();
if (method === "get" || method === "delete") {
return instance({url, method, params: data, ...config});
}
return instance({url, method, data, ...config});
};
// 导出
export default http;
view层
存放界面,如LginView.vue,对应的是一整个界面
<template>
<div class="login_container">
<div class="login_box">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: "LoginView",
data() {
return {};
},
methods: {},
components: {}
};
</script>
<style scoped>
.login_container {
width: 100%;
height: 100%;
background-image: url('../assets/loginBackgroud.jpg');
background-position: center center;
background-attachment: fixed;
background-repeat: no-repeat;
background-size: cover;
}
.login_box {
width: 460px;
height: 360px;
background-color: white;
border-radius: 3px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
position: absolute
}
</style>