简介:
前端选用VUE是因为它比较简单,容易上手,对于不熟悉前端的小伙伴很适合。对于软件发展来说,后端技术已趋于成熟和稳定,在功能已稳定的情况下,客户会把要求更多的放在页面的美观和合理排版布局上,学习一下前端,特别是自己设计一个页面,有助于对前端的了解和对美观设计的培养。
一、搭建VUE项目
1.搭建VUE基础框架
1.1 安装node.js
安装过程中记得勾选Add to path,安装完成后再cmd命令行输入:node -v 和 npm -v 如果分别显示版本号则安装成功。

1.2 安装vue脚手架vue-cli
输入以下命令:npm install -g vue-cli (其中-g表示全局安装)
1.3 初始化一个项目
在cmd命令行进入要安装项目的文件夹,输入以下命令:vue init webpack projectName (其中projectName填写你的项目名称)比如下图,进入Project文件夹,按着问号?后的提示操作,没有用红字写备注的都是默认或者选NO的,最后提示 Project initialization finished 代表成功。

然后我们可以看到在d:project下生成的项目文件夹:

1.4 安装依赖组件
通常我们安装组件方法是先进入项目目录下(比如这里是命令行进入yytf文件夹):输入命令: npm install xxx (比如安装jquery:xxx就填jquery),但我们这里尽量不要通过这种方式安装,还是那个问题,为了减小webpack打包后vendor.js的大小,我们通过cdn方式引入,比如index.html中引入:<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
1.5 启动服务
通过命令: npm run dev 如果没有报错,就可以通过提示的链接在浏览器登录,看到“Welcome to Your Vue.js App”表示登录成功

2.路由模块
2.1 index.html
引入:<script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
2.2 webpack.base.conf.js
在module.exports = {}中最后加上
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'axios': 'axios',
'vue-resource': 'VueResource'
}
2.3 routes.js
两种引入方式:

//>普通路由引入方式(所有的vue模块的js文件都打包进vendor.js和app.js中)
//import Articles from './components/Articles'
//import Topics from './components/Topics'
//import AboutMe from './components/AboutMe'
//import TimeLine from './components/TimeLine'
//import Pictures from './components/Pictures'
//>按需加载路由引入方式(各个vue模块的js文件分别打包进0.xxx.js、1.xxx.js、2.xxx.....)
const Articles = r => require.ensure([], () => r(require('./components/Articles')));
const Topics = r => require.ensure([], () => r(require('./components/Topics')));
const AboutMe = r => require.ensure([], () => r(require('./components/AboutMe')));
const TimeLine = r => require.ensure([], () => r(require('./components/TimeLine')));
const Pictures = r => require.ensure([], () => r(require('./components/Pictures')));
//构建vue-router实例(这里的VueRouter要和2.2中的名字对应):
export default new VueRouter({
mode:"history",
routes: [
{path: '/',name: 'Articles',component: Articles},
{path: '/topics',name: 'Topics',component: Topics},
{path: '/aboutMe',name: 'AboutMe',component: AboutMe},
{path: '/timeLine',name: 'TimeLine',component: TimeLine},
{path: '/pictures',name: 'Pictures',component: Pictures}
]
})

这里有个坑,如果我们不加mode:"history",那么浏览器的路径会出现#不美观,如果我们加上mode:"history"后,在本地环境下一切都是正常的,但部署到服务器的nginx上跳转后如果刷新页面就会出现404了,这是因为那是因为在history模式下,只是动态的通过js操作window.history来改变浏览器地址栏里的路径,并没有发起http请求,但是当我直接在浏览器里输入这个地址的时候,就一定要对服务器发起http请求,但是这个目标在服务器上又不存在,所以会返回404而本地开发时用的服务器为node,Dev环境中自然已配置好了。所以要在nginx.conf里面做一些配置:

location / {
root html;
index index.html;
if (!-e $request_filename){
rewrite ^/(.*) /index.html last;
break;
}
}

2.4 使用路由
2.4.1在main.js中引入之前的routes.js(./routes这个相对路径视情况而定):import router from './routes'
2.4.2在main.js中把路由挂载到vue实例上(注意vue对象中左边的router不能随便更换名称):

new Vue({
el: '#app',
axios,
router:router,
components: { App },
template: '<App/>'
})

2.4.3在app,vue中使用router-view标签:
<template>
<page-header></page-header>
<router-view></router-view>
</template>
2.4.4在PageHeader.vue中使用导航标签做跳转

<div class="nav">
<ul class="wow pulse navul">
<li>
<router-link to="/" exact>技术文章</router-link>
<router-link to="/topics" exact>随笔杂谈</router-link>
<router-link to="/timeLine" exact>时光轴</router-link>
<router-link to="/aboutMe" exact>关于我</router-link>
<router-link to="/pictures" exact>图集</router-link>
</li>
</ul>
</div>

2.5 路由跳转前的权限校验(需要在routes.js中加meta:{requireAuth: true})

router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) { //该路由需要登录权限
if (window.localStorage.getItem('token')) {//进入后台
next();
}else {//返回前台
next({
path: '/',
query: {redirect: to.fullPath} //将跳转的路由path作为参数,登录成功后跳转到该路由
})
}
}
else {//该路由不需要登录权限
next();
}
})

3.axios模块
3.1 简介:
Axios是一个基于promise的HTTP库,可以用在浏览器和node.js中:
>从浏览器中创建 XMLHttpRequests
>支持Promise API
>从node.js 创建 http 请求
>拦截请求和响应
>转换请求数据和响应数据
3.2 配置:
3.2.1 index.html:引入<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
3.2.2 webpack.base.conf.js 在module.exports = {}中最后加上
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'axios': 'axios',
'vue-resource': 'VueResource'
}
3.2.3 main.js
设置为Vue的内置对象: Vue.prototype.$axios = axios;
设置请求默认前缀: axios.defaults.baseURL = 'http://localhost:8080/blog/';
这里也可以利用上面方法设置请求的header信息,具体可以百度
3.3 使用

getArticles: function() {
var _this = this;
this.$axios.post("article/list", {
title: _this.xxx
})
.then(function(result) {
var response = result.data;
if (response.statusCode == "200") {} else {}
})
});

3.4 前端后台管理对请求拦截:
在main.js同级目录新建http.js:

import router from './routes'
// axios 配置
axios.defaults.timeout = 5000
axios.defaults.baseURL = process.env.BASE_API
// http request 拦截器
axios.interceptors.request.use(
config => {
if (window.localStorage.getItem('token')) {
config.headers.Authorization = window.localStorage.getItem('token');
}
return config
},
err => {
return Promise.reject(err)
}
)
// http response 拦截器
axios.interceptors.response.use(
response => {
return response;
},
error => {
if (error.response) {
switch (error.response.status) {
case 401:
// 返回 401 清除token信息并跳转到登录页面
window.localStorage.setItem('token', null)
router.replace({
path: '/',
query: {redirect: router.currentRoute.fullPath}
})
}
}
return Promise.reject(error.response.data) //返回接口返回的错误信息
}
);
export default axios

在main.js中:

import Axios from './http'
Vue.prototype.$axios = Axios;
axios.defaults.baseURL = 'http://localhost:8080/blog/';
new Vue({
el: '#app',
axios,
router,
components: { App },
template: '<App/>'
})

到这里我们已经在前端做了路由跳转拦截和后台请求拦截,因为不懂前端,所以感觉上安全性应该能得到保障,但到底真的安不安全还真不知道,以后会慢慢多了解一下。
4.富文本模块
4.1 wangeditor(因为这个比较简单,功能一般,所以我放在评论和留言里用):

<div id="editorMneuElem"></div>
<div id="editorElem" style="text-align:left;height: 150px;"></div>
mounted: function() {
//wangEditor配置,根据自己情况配置,图片也可以使用相对路径或cdn方式
this.editor = new E('#editorMneuElem','#editorElem')
this.editor.customConfig.menus = ['head','foreColor','emoticon','code'];
this.editor.customConfig.colors = ['#FF0000','#0000FF','#00FF00','#FF6EB4','#FFA500','#A020F0','#00FF7F'];
this.editor.customConfig.onchange = (html) => {this.editorContent = html}
var icon = new Array();
var def = new Array();
for(var i=1;i<=20;i++){
icon[i-1] = {src:'http://xxx.com/images/emoji/'+(i-1)+'.gif'};
}
for(var i=1;i<=10;i++){
def[i-1] = {src:'http://xxx.com/images/huaji/'+(i-1)+'.png'};
}
this.editor.customConfig.emotions = [
{title: "默认",type: "image",content: icon},
{title: "滑稽",type: "image",content: def}
];
this.editor.create();
}

4.2 tinymce(这个功能比较强大,可以直接从word拖拽图片或有格式的文档,所以我放在添加文章里使用,界面也比较漂亮,可惜官方文档是英文):
这里可以参考网上的大神的配置,贴一下我填完坑的代码(红色部分是上传图片后回显在编辑器里的图片路径,这个使用相对路径时很容易错,推荐使用绝对路径的cdn,曾经搞这个tinymce的图片上传堵了我好久):

<template>
<div><textarea :id= "id"></textarea></div>
</template>
<script>
import tinymce from "../../static/tinymce/tinymce.min.js";
import "../../static/tinymce/themes/modern/theme.min.js";
import "../../static/tinymce/plugins/autosave/plugin.min.js";
import "../../static/tinymce/plugins/colorpicker/plugin.min.js";
import "../../static/tinymce/plugins/codesample/plugin.min.js";
import "../../static/tinymce/plugins/contextmenu/plugin.min.js";
import "../../static/tinymce/plugins/emoticons/plugin.js";
import "../../static/tinymce/plugins/insertdatetime/plugin.min.js";
import "../../static/tinymce/plugins/image/plugin.min.js";
import "../../static/tinymce/plugins/imagetools/plugin.min.js";
import "../../static/tinymce/plugins/lists/plugin.min.js";
import "../../static/tinymce/plugins/link/plugin.min.js";
import "../../static/tinymce/plugins/paste/plugin.min.js";
import "../../static/tinymce/plugins/fullpage/plugin.min.js";
import "../../static/tinymce/plugins/fullscreen/plugin.min.js";
import "../../static/tinymce/plugins/preview/plugin.min.js";
import "../../static/tinymce/plugins/media/plugin.min.js";
import "../../static/tinymce/plugins/table/plugin.min.js";
import "../../static/tinymce/plugins/textcolor/plugin.min.js";
import "../../static/tinymce/plugins/textpattern/plugin.min.js";
import "../../static/tinymce/plugins/wordcount/plugin.min.js";
import "../../static/tinymce/plugins/toc/plugin.min.js";
import "../../static/tinymce/langs/zh_CN.js";
const INIT = 0;
const CHANGED = 2;
var EDITOR = null;
export default {
data() {
return {status: INIT, id: "editor-" + new Date().getMilliseconds()};
},
props: {
value: {default: "",type: String},
url: {default: "http:",type: String},
accept: {default: "image/jpg, image/jpeg, image/png, image/gif",type: String},
maxSize: {default: 2097152,type: Number}
},
watch: {
value: function(val) {
if (this.status === INIT || tinymce.activeEditor.getContent() !== val) {tinymce.activeEditor.setContent(val);}
this.status = CHANGED;
}
},
mounted: function() {
console.log("editor");
window.tinymce.baseURL = '/static/tinymce';
const self = this;
const setting = {
selector: "#" + self.id,
language: "zh_CN",
language_url: "../../static/tinymce/langs/zh_CN.js",
init_instance_callback: function(editor) {
EDITOR = editor;
console.log("Editor: " + editor.id + " is now initialized.");
editor.on("input change undo redo", () => {var content = editor.getContent();self.$emit("input", content);});
},
plugins: [],
images_upload_handler: function(blobInfo, success, failure) {
if (blobInfo.blob().size > self.maxSize) {failure("文件体积过大");}
if (self.accept.indexOf(blobInfo.blob().type) > 0) {uploadPic();} else {failure("图片格式错误");}
function uploadPic() {
const xhr = new XMLHttpRequest();
const formData = new FormData();
xhr.open("POST", self.url, true);
xhr.withCredentials = true;//允许带认证信息的配置.解决跨域问题前端需要的配置
formData.append("file", blobInfo.blob());
xhr.send(formData);
xhr.onload = function() {
if (xhr.status !== 200) {self.$emit("on-upload-fail"); failure("上传失败: " + xhr.status); return;}// 抛出 'on-upload-fail' 钩子
const json = JSON.parse(xhr.responseText);
self.$emit("on-upload-complete", [json, success, failure]);// 抛出 'on-upload-complete' 钩子
success(json.data.file.filePath);
};
}
},
setup: (editor) => {
// 抛出 'on-ready' 事件钩子
editor.on('init', () => {self.loading = false; self.$emit('on-ready'); editor.setContent(self.value);})
// 抛出 'input' 事件钩子,同步value数据
editor.on('input change undo redo', () => {self.$emit('input', editor.getContent())})
},
height: 500,
theme: 'modern',
menubar: true,
toolbar: `styleselect | fontselect | formatselect | fontsizeselect | forecolor backcolor | bold italic underline strikethrough hr | table | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | media preview removeformat code link fullscreen | undo redo | image emoticons codesample`,
plugins: `paste importcss image code codesample table advlist fullscreen link media lists textcolor colorpicker hr preview emoticons`,
codesample_languages: [
{text: 'HTML/XML', value: 'markup'},{text: 'JavaScript', value: 'javascript'},{text: 'CSS', value: 'css'},{text: 'PHP', value: 'php'},{text: 'Python', value: 'python'},{text: 'Java', value: 'java'},{text: 'C', value: 'c'},{text: 'C++', value: 'cpp'}
],
// codesample_content_css:"../../static/common/css/prism.css",
// CONFIG
forced_root_block: 'p',
force_p_newlines: true,
importcss_append: true,
// CONFIG: ContentStyle 这块很重要, 在最后呈现的页面也要写入这个基本样式保证前后一致, `table`和`img`的问题基本就靠这个来填坑了
content_style: `
* { padding:0; margin:0; }
html, body { height:100%; }
img { max-width:100%; display:block;height:auto; }
a { text-decoration: none; }
iframe { width: 100%; }
p { line-height:1.6; margin: 0px; }
table { word-wrap:break-word; word-break:break-all; max-width:100%; border:none; border-color:#999; }
.mce-object-iframe { width:100%; box-sizing:border-box; margin:0; padding:0; }
ul,ol { list-style-position:inside; }
`,
insert_button_items: 'image link | inserttable',
// CONFIG: Paste
paste_retain_style_properties: 'all',
paste_word_valid_elements: '*[*]', // word需要它
paste_data_images: true, // 粘贴的同时能把内容里的图片自动上传,非常强力的功能
paste_convert_word_fake_lists: false, // 插入word文档需要该属性
paste_webkit_styles: 'all',
paste_merge_formats: true,
nonbreaking_force_tab: false,
paste_auto_cleanup_on_paste: false,
// CONFIG: Font
fontsize_formats: '10px 11px 12px 14px 16px 18px 20px 24px 26px 28px 32px',
// CONFIG: StyleSelect
style_formats: [
{title: '首行缩进',block: 'p',styles: { 'text-indent': '2em' }},
{title: '行高',items: [{title: '1', styles: { 'line-height': '1' }, inline: 'span'},{title: '1.5', styles: { 'line-height': '1.5' }, inline: 'span'},{title: '2', styles: { 'line-height': '2' }, inline: 'span'},{title: '2.5', styles: { 'line-height': '2.5' }, inline: 'span'},{title: '3', styles: { 'line-height': '3' }, inline: 'span'}]}
],
// FontSelect
font_formats: `微软雅黑=微软雅黑;宋体=宋体;黑体=黑体;仿宋=仿宋;楷体=楷体;隶书=隶书;幼圆=幼圆;Andale Mono=andale mono,times;Arial=arial, helvetica,sans-serif;
Arial Black=arial black, avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;
Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;
Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats`,
// Tab
tabfocus_elements: ':prev,:next',
object_resizing: true,
// Image
imagetools_toolbar: 'rotateleft rotateright | flipv fliph | editimage imageoptions'
};
tinymce.init(setting);
},
beforeDestroy: function() {
tinymce.get(this.id).destroy();
}
};
</script>

5.点击特效
这个也是搬运的,JS博大精深,研究了一下,其实也就是使用了一下animate.css的消退特效,在main.js中:

/* 鼠标点击特效 -start*/
var getColor = function() {
var randomColor=['#0000FF','#FFA500','#FF0000','#A020F0','#00F5FF','#008B00','#FF6A6A','#FF00FF','#00FF00','#FFB90F'];var colorIndex = parseInt(Math.random()*10);return randomColor[colorIndex];};
var fnTextPopup = function(arr, options) {if (!arr || !arr.length) {return;}var index = 0;document.documentElement.addEventListener("click", function(event) {var x = event.pageX,y = event.pageY;
var eleText = document.createElement("span");eleText.className = "text-popup";this.appendChild(eleText);if (arr[index]) {eleText.innerHTML = arr[index];} else {index = 0;eleText.innerHTML = arr[0];}
eleText.addEventListener("animationend", function() {eleText.parentNode.removeChild(eleText);});eleText.style.left = x - eleText.clientWidth / 2 + "px";eleText.style.top = y - eleText.clientHeight + "px";
index++;var textPopupElement = document.getElementsByClassName("text-popup")[0];textPopupElement.style.color = getColor();});
};
fnTextPopup(["富强","民主","文明","和谐","自由","平等","公正","法治","爱国","敬业","诚信","友善"]);
/* css */
.text-popup {
animation: textPopup 800ms;
user-select: none;
white-space: nowrap;
position: absolute;
z-index: 999;
font-size: 24px;
}
@keyframes textPopup {
0%,
100% {
opacity: 0;
}
5% {
opacity: 1;
}
100% {
transform: translateY(-50px);
}
}
/* 鼠标点击特效 -end*/

二、项目优化
由于刚做完项目,然后上线后,第一次打开首页用了18s+,吓到我了,然后花了很长时间在寻找页面优化的方法,最后效果也是刚刚的:
1.静态资源尽量使用CDN
比如index.html中引入 <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.min.js"></script> 替换掉使用 npm install vue 这种方式
2.VUE懒加载
在上面介绍路由中已详细讲解了,贴张使用全部加载和按需加载的张图,可以看到上面的这种方式举个栗子:打开首页只会加载0.xxx.js,当打开第二个页面再加载1.xxx.js,当代码量和引用资源比较多时,可以极大减轻首页的加载压力

3.开启GZIP打包
在config/index.js的官方注释里说了开启gzip前要先安装 compression-webpack-plugin
所以先运行:npm install --save-dev compression-webpack-plugin
再在index.js中设置 productionGzip: true
4.JS和CSS压缩成min版
一般vue打包的js自带压缩功能,如果你像我一样把css都提取到common.css中,你可以使用网上的css在线压缩工具,放到common.min.css中做生产环境的css包
5.抽取出VUE中的重复代码作公共模块
这个自己视情况而定。
6.还有很多优化的方法可以百度一下
貌似其它都是些无关紧要的东西,也没什么说的了,就这样吧!下篇开始搭建后台项目,最近心情很差,也不想说什么话,只想一个人安静的听着歌,做着自己的事。
腾讯云最新服务器活动--云服务器免费送。
本文介绍了从零开始使用Vue.js搭建前端项目的过程,包括Vue基础框架的搭建、路由模块配置、axios的集成与使用,以及项目优化技巧如静态资源CDN、Vue懒加载和GZIP压缩等。此外,还提到了前端路由的权限校验和富文本编辑器的配置。
3209

被折叠的 条评论
为什么被折叠?



