初次写技术博客大菜鸟一枚,都不知道从何下手,只要是苦逼码农,会撸代码写BUG就万事大吉。可思来想去,还是要写点什么关于前端技术的内容,一方面提高自己的写作水平,另一方面加深对现有前端知识的掌握程度以及研究新的前沿技术做准备。更要学会回顾,思考,总结。本人搜罗一遍,决定拿出2年前利用业余时间做过的一个小项目,经过自己对本项目前端框架版本升级和架构改良后,可供前端入门小伙伴学习,练练手。文章末尾会附上github源码地址供下载参考,欢迎大大们吐槽,提
issue & star
。首先来看下多页面应用开发的项目架构。
│ vue.config.js // webpack配置
│ vue.util.js // 打包多页面配置
├─public
│ favicon.ico // 图标
│ index.html // 入口html文件
│ share.png // 微信分享提示
├─src
│ ├─assets
│ │ ├─css
│ │ │ common.css // 公共样式文件
│ │ ├─img // 存放公共图片文件夹
│ │ └─js
│ │ api.js // 封装所有API接口调用方法
│ │ common.js // 常用JS方法
│ │ export.js // 外部调用统一出口
│ │ fastclick.js // 移动端点击延迟事件处理
│ │ network.js // axios封装与拦截器配置
│ │ url.js // 自动部署服务器环境
│ ├─components
│ │ backTop.vue // 返回顶部组件
│ │ categoryList.vue // 商品分类组件
│ │ errNotice.vue // 错误弹框提示信息组件
│ │ jumpCoupon.vue // 跳转优惠券
│ │ loading.vue // 页面初始化加载数据的动画组件
│ │ qrcodePop.vue // 生成二维码弹框
│ │ shopList.vue // 商品列表组件
│ │ soldOut.vue // 请求数据错误展示占位图
│ ├─mock
│ │ index.js // 引入mockjs模拟数据
│ └─pages
│ ├─coupon
│ │ coupon.html // 领券页面结构
│ │ coupon.js // 领券页面入口文件,加载各种公用组件
│ │ coupon.vue // 领券页
│ ├─detail
│ │ detail.html // 商品详情结构
│ │ detail.js // 商品详情入口文件,加载各种公用组件
│ │ detail.vue // 商品详情页
│ ├─index
│ │ index.html // 首页结构
│ │ index.js // 首页入口文件,加载各种公用组件
│ │ index.vue // 首页
│ └─search
│ search.html // 商品分类/关键词搜索页面结构
│ search.js // 商品搜索入口文件,加载各种公用组件
│ search.vue // 商品搜索页
└─static // 存放静态资源文件夹
└─img // 静态图片文件夹
npm install -g @vue/cli
# 安装指定版本
npm install -g @vue/cli@3.11.0
# OR
yarn global add @vue/cli
vue -V
vue create woyouzhe
cd woyouzhe
npm run serve
在浏览器地址栏输入:http://localhost:8080/
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const isProduction = process.env.NODE_ENV === 'production'
module.exports = {
publicPath: isProduction ? '/dist/' : '/', // 部署生产环境和开发环境下的URL:可对当前环境进行区分
lintOnSave: false, // 是否在代码保存时进行eslint检测
productionSourceMap: false, // 是否在构建生产包时生成sourceMap文件,false将提高构建速度
devServer: { // webpack-dev-server 相关配置
port: '8090', // 端口号
https: false, // 关闭https
hotOnly: false, // 取消热更新
// proxy: { // 使用代理
// '/api': {
// target: '//www.woyouzhe.com', // 目标代理服务器地址
// changeOrigin: true, // 允许跨域
// pathRewrite:{
// '^/api': '' // 重写路径,需要设置重写的话,要在后面的调用接口前加上/api来代替target
// }
// }
// }
},
// webpack手动配置
configureWebpack: (config) => {
if (isProduction) {
// 取消webpack警告的性能提示
config.performance = {
hints: 'error',
maxAssetSize: 300000, // 生成文件的最大体积,整数类型(以字节为单位)
maxEntrypointSize: 500000, // 入口起点的最大体积,整数类型(以字节为单位)
assetFilter: function(assetFilename) { // 只给出js文件的性能提示
return assetFilename.endsWith('.js');
}
}
config.plugins.push(
new UglifyJsPlugin({
uglifyOptions: {
compress: {
drop_debugger: true, // 生产环境自动删除debugger
drop_console: true, // 生产环境自动删除console
},
warnings: false // 删除无用代码,不提示警告
},
sourceMap: false, // 关闭错误消息位置映射到模块
parallel: true, // 启用多进程并行运行
})
)
}
}
}
多页面配置,在项目根目录新建vue.util.js文件,如下:
const path = require('path')
const glob = require('glob')
const startPath = '/src/pages/'
const pagePath = path.resolve(__dirname, '.' + startPath)
exports.pages = function () {
let entryFiles = glob.sync(pagePath + '/**/*.html')
let obj = {}
entryFiles.forEach(filePath => {
let dirPath = filePath.substring(0, filePath.lastIndexOf('/'))
let dirName = dirPath.substring(dirPath.lastIndexOf('/') + 1)
let filename = filePath.substring(filePath.lastIndexOf(startPath) + startPath.length, filePath.lastIndexOf('/'))
if (filename.endsWith(dirName)) {
obj[filename] = {
entry: filePath.substring(0, filePath.lastIndexOf('.html')) + '.js',
template: filePath.substring(0, filePath.lastIndexOf('.html')) + '.html'
}
}
})
return obj
}
配置完成,在vue.config.js文件引入vue.util.js,如下图:
多页面配置完成后,在src文件夹下创建pages文件夹,里面放每个页面模块,每个界面由html、js、vue三部分组成,如下图:
let baseUrl = ''
if (process.env.NODE_ENV == 'development') {
console.log('dev')
//开发、测试环境
baseUrl = '//yapi.demo.qunar.com/mock/95397/api'
} else if (process.env.NODE_ENV == 'production') {
console.log('prod')
// 生产环境
baseUrl = '//www.woyouzhe.com/api'
}
export {
baseUrl
}
network.js是axios封装与拦截器配置,先安装axios,再引入到network.js,代码如下:
npm install -S axios
import axios from 'axios'
import { baseUrl } from './url'
let service = axios.create({
baseURL: baseUrl, // 请求前缀
timeout: 55000, // 请求超时时间
})
// 设置 post 默认 Content-Type
service.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
// 添加请求拦截器
service.interceptors.request.use(
config => {
// 这里可以添加head 部分
return config
},
err => {
// 请求错误处理
return Promise.reject(err)
})
// 添加响应拦截器
service.interceptors.response.use(
response => {
let { data } = response
return data
},
err => {
// 响应错误处理
return Promise.reject(err)
})
export default service
api.js是封装所有API接口调用方法,后面会具体讲解怎么调用这些方法,获取服务端返回的数据,代码如下:
import network from './network'
import { baseUrl } from './url'
// 商品列表接口
export function getShopList(params) {
return network({
url: '/shoplist',
method: 'get',
params
});
}
common.js是常用JS方法,代码如下:
let Rxports = {
// 遍历数组与对象,回调的第一个参数为索引或键名,第二个或元素或键值
each: function (obj, fn) {
var me = this;
if (obj) { // 排除null, undefined
var i = 0
if (me.isArrayLike(obj)) {
for (var n = obj.length; i < n; i++) {
if (fn(i, obj[i]) === false)
break
}
} else {
for (i in obj) {
if (obj.hasOwnProperty(i) && fn(i, obj[i]) === false) {
break
}
}
}
}
},
// 获取url参数
getUrlParam: function () {
var url = window.location.search, // 获取url中"?"符后的字串
theRequest = new Object();
if (url.indexOf("?") != -1) {
var str = url.substr(1), strs = str.split("&");
for(var i = 0; i < strs.length; i ++) {
theRequest[strs[i].split("=")[0]] = decodeURI(strs[i].split("=")[1]);
}
}
return theRequest;
},
// 页面单位rem
rem: function () {
var docEl = document.documentElement,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
recalc = function () {
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
if (clientWidth >= 750) {
docEl.style.fontSize = '100px';
} else {
docEl.style.fontSize = 100 * (clientWidth / 750) + 'px';
}
};
recalc();
window.addEventListener(resizeEvt, recalc, false);
},
// 自定义模态框
modalHelper: {
scrollTop: '',
bodyCls: 'modal-open',
srcollBody: (document.scrollingElement || document.body),
afterOpen: function() {
var me = this;
this.scrollTop = this.srcollBody.scrollTop;
document.body.classList.add(me.bodyCls);
document.body.style.top = -this.scrollTop + 'px';
},
beforeClose: function () {
var me = this;
document.body.classList.remove(me.bodyCls);
this.srcollBody.scrollTop = this.scrollTop;
document.body.style.top = '0';
}
},
// 去除空格
trim: function (strs) {
return strs.replace(/(^\s*)|(\s*$)/g, '');
}
}
export default Rxports
fastclick.js是处理移动端点击事件之后,出现300ms延迟,可以直接安装引入使用,我是直接下载js插件到本地再引入。
// 安装
npm install -S fastclick
// 引入
import FastClick from 'fastclick'
// 使用
FastClick.attach(document.body);
export.js是外部调用模块统一出口,代码如下:
import '../css/common.css'
import C from './common'
// 解决click点击300毫秒延时问题
import FastClick from './fastclick'
// FastClick.attach(document.body);
!(function(){
new FastClick(document.body)
})
export default C
以上基本架构搭建完成,下面终于要进入页面功能开发,心情十分鸡冻,敲代码的日子才刚刚开始。一般开发项目之前,需要先分析页面需求,有哪些功能模块,技术选型,有无技术难点(是否可替代),哪些组件可复用,手机适配,前端性能考虑,是否多人协作开发,与后端约定API接口规则等等。时间过得真快,都凌晨2点了,就写到这吧。
下一篇:基于vue-cli3 + axios 构建多页面应用H5移动端电商网站(下)
附上github源码地址:https://github.com/jackchen0120/woyouzhe