一、创建项目及配置
1.1 vue cli2 创建项目
vue init webpack project
npm install
npm run dev
1.2 vue cli3 创建项目
vue create project
选择配置,看个人项目需求
TypeScript 支持使用 TypeScript 书写源码
Progressive Web App (PWA) Support PWA 支持。
Router 支持 vue-router 。
Vuex 支持 vuex 。
CSS Pre-processors 支持 CSS 预处理器。
Linter / Formatter 支持代码风格检查和格式化。
Unit Testing 支持单元测试。
E2E Testing 支持 E2E 测试。
进入到项目根目录
cd project
启动项目
npm run serve
二、安装 element-UI
npm i element-ui -S
2.1main.js 引入
2.1.1 全局引入
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(Element)
2.1.2 按需引入
首先,安装 babel-plugin-component
npm install babel-plugin-component -D
然后,将 .babelrc 修改为:
{
"presets": [
["es2015", {
"modules": false
}]
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
接下来,如果你只希望引入部分组件,比如 Button 和 Select,那么需要在 main.js 中写入以下内容:
import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';
Vue.component(Button.name, Button);
Vue.component(Select.name, Select);
或写为
- Vue.use(Button)
- Vue.use(Select)
三、安装 vuex
npm i vuex -s
3.1 /src/store 下面的 index.js 中
import Vue from 'vue'
import Vuex from 'vuex'
//挂载 Vuex
Vue.use(Vuex)
//创建 VueX 对象
const store = new Vuex.Store({
state:{name: 'helloVueX',
},
mutations:{},
actions:{},
modules:{}
})
export default store
3.2 main.js 引入
将 store 挂载到当前项目的 Vue 实例当中去
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store, //store:store 和 router 一样,将我们创建的 Vuex 实例挂载到这个 vue 实例中
render: h => h(App)
})
3.3 在组件中使用 Vuex
<template>
<div id='app'>
name:
<h1>{{ $store.state.name }}</h1>
</div>
</template>
methods:{
add(){
console.log(this.\$store.state.name)
}
},
四、配置scss环境
4.1.首先安装依赖
npm install node-sass sass-loader --save-dev
4.2.找到 build 中 webpack.base.conf.js,在 rules 中添加 scss 规则
{
test: /\.scss\$/,
loaders: ['style', 'css', 'sass']
}
4.3.在 vue 文件中使用
<style lang='scss'>
</style>
4.4 在 vue 项目全局中引入 scss
1.全局引用时需要安装 sass-resources-loader
npm install sass-resources-loader --save-dev
2.修改 build 中的 utils.js
将scss: generateLoaders('sass')
修改为
scss: generateLoaders('sass').concat({
loader: 'sass-resources-loader',
options: {
//你自己的 scss 全局文件的路径
resources: path.resolve(\_\ _dirname, '../src/style/common.scss')
}
})
如果上面的不能正常编译
//配置 sass 编译路径
function generateSassResourceLoader() {
let loaders = [
cssLoader,
'sass-loader',
{
loader: 'sass-resources-loader',
options: {
// 多个文件时用数组的形式传入,单个文件时可以直接使用 path.resolve(__dirname, '../static/style/common.scss'
resources: path.resolve(__dirname, '../src/style/common.scss')
}
}
];
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
});
} else {
return ['vue-style-loader'].concat(loaders);
}
}
在 cssLoaders 里面引入
sass:generateSassResourceLoader(),//新加的
scss:generateSassResourceLoader(),//新加的
4.5 引入全局的 sass
npm install --s node-sass sass-loader
1.首先你需要
npm install --s sass-resources-loader
2.在 build 目录下找到 utils.js 文件
Module build failed: TypeError: this.getResolve is not a function at Object.loader
安装 node-sass 运行报错
vue 安装 node-sass 编译报错
安装node-scss报错
安装 node-scss 报错
在搭建 vue 脚手架 或者是在 vue 项目中,想使用 sass 的功能,
npm install node-sass --save-dev //安装 node-sass
npm install sass-loader --save-dev //安装 sass-loader
npm install style-loader --save-dev //安装 style-loader
这是因为当前 sass 的版本太高,webpack 编译时出现了错误,这个时候只需要换成低版本的就行,下面说一下修改方法,很简单,如下,找到 package.json 文件,里面的 "sass-loader"的版本更换掉 就行了。
将 "sass-loader": "^8.0.0",更换成了 "sass-loader": "^7.3.1",
也可以先卸载当前版本,然后安装指定的版本
卸载当前版本
npm uninstall sass-loader
安装
npm install sass-loader@7.3.1 --save-dev
五、配置less环境
安装 less 和 less-loader
npm install less less-loader --save
修改 webpack.base.config.js 文件,配置 loader 加载依赖,让其支持外部的 less,在原来的代码上添加
// 此种方法在控制台中标签样式显示的是style标签样式
{
test: /\.less$/,
loader: "style-loader!css-loader!less-loader",
}
// 可以在控制台中看到当前标签样式来自于哪个less文件
{
test: /\.less$/,
loader: "style-loader!css-loader!less-loader",
options: {
sourceMap: true
}
}
在 vue 文件中的 style 标签中添加 lang="less"即可在标签中使用 less,或者外部引入 less
六、引入font-awesome
npm install font-awesome --save
然后在 main.js 引入 font-awesome/css/font-awesome.min.css 即可。
7、vue 配置网站的 ico
7.1方式一:
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon" />
7.2 方式二
webpack.dev.conf.js
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
favicon: 'favicon.ico', // 新增
inject: true
}),
webpack.prod.conf.js
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency',
favicon: 'favicon.ico' // 新增
}),
八、引入自定义公共样式
在 router/index.js 里面引入公共样式
import 'bootstrap/dist/css/bootstrap.css' //引入 bootstrap
import 'bootstrap-vue/dist/bootstrap-vue.css'
import '@/common/common.css'
import '@/common/index.css'
九、路由按需加载
const port = () => import('@/pages/port') //入口页面、
const router = new Router({
// mode: 'history',
routes: [
{
path: '/',
name: 'port',
component: resolve => require.ensure([], () => resolve(require('@/pages/port')), 'port'),
}]
})
十、全局自定义方法
main.js 里面挂载方法到 vue.prototype
Vue.prototype.isMobile = function() {
let flag = navigator.userAgent.match(
/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
)
return flag;
}
组件里面使用
mounted: function() {
this.isMobile();
}
十一、引入接口文件暴露到全局
11.1 在 main.js 里面引入接口暴露到全局'
// 接口暴露在全局
import { server } from './config/api'
Vue.prototype.\$server = server;
//api.js
export const server = {
getContentMenu: (paramObj)=>fetch('/content/menu',paramObj),//内容详情查询
getContentListPort: (paramObj)=>fetch('/content/list/'+paramObj),//入口页面接口
getContentList:(paramObj)=>fetch('/content/list/'+paramObj),//内容详情查询
getPageviews:(paramObj)=>fetch('/webpage/1/view',paramObj)//流量统计接口
}
组件里面使用:
methods: {
getPageviews() {
var that = this;
let params = {
pageId: that.pageId,
pageUrl: that.pageUrl,
};
that.\$server.getPageviews(params).then(response => {})
}
}
##### 11.2 **axios** 方法封装,整个 **api.js**
import axios from 'axios';
axios.defaults.timeout = 5000;
axios.defaults.baseURL =''; //填写域名
//http request 拦截器
axios.interceptors.request.use(
config => {
config.data = JSON.stringify(config.data);
config.headers = {
'Content-Type':'application/x-www-form-urlencoded'
}
return config;
},
error => {
return Promise.reject(err);
}
);
//响应拦截器即异常处理
axios.interceptors.response.use(response => {
return response
}, err => {
if (err && err.response) {
switch (err.response.status) {
case 400:
console.log('错误请求')
break;
case 401:
console.log('未授权,请重新登录')
break;
case 403:
console.log('拒绝访问')
break;
case 404:
console.log('请求错误,未找到该资源')
break;
case 405:
console.log('请求方法未允许')
break;
case 408:
console.log('请求超时')
break;
case 500:
console.log('服务器端出错')
break;
case 501:
console.log('网络未实现')
break;
case 502:
console.log('网络错误')
break;
case 503:
console.log('服务不可用')
break;
case 504:
console.log('网络超时')
break;
case 505:
console.log('http版本不支持该请求')
break;
default:
console.log(`连接错误${err.response.status}`)
}
} else {
console.log('连接到服务器失败')
}
return Promise.resolve(err.response)
})
/**
- 封装get方法
- @param url
- @param data
- @returns {Promise}
*/
export function fetch(url,params={}){
return new Promise((resolve,reject) => {
axios.get(url,{
params:params
})
.then(response => {
resolve(response.data);
})
.catch(err => {
reject(err)
})
})
}
/**
- 封装post请求
- @param url
- @param data
- @returns {Promise}
*/
export function post(url,data = {}){
return new Promise((resolve,reject) => {
axios.post(url,data)
.then(response => {
resolve(response.data);
},err => {
reject(err)
})
})
}
/**
- 官网接口请求封装
- @param url
- @param data
- @returns {Promise}
*/
export const server = {
getContentMenu: (paramObj)=>fetch('/content/menu',paramObj),//内容详情查询
getContentListPort: (paramObj)=>fetch('/content/list/'+paramObj),//入口页面接口
getContentList:(paramObj)=>fetch('/content/list/'+paramObj),//内容详情查询
getPageviews:(paramObj)=>fetch('/webpage/1/view',paramObj)//流量统计接口
}
## 十二、provide /inject 完美解决不跳转不闪动页面刷新
原理:此方法使用的是 v-if 来控制 router-view 的显示或隐藏,v-if 从 false 变为 true 时,vue 会重新渲染 router-view 区域,所以当参数变化时,只需让 v-if 从 true => false => true,就能实现页面刷新。
##### 12.1 找到route-view
//App.vue
<div id="app">
<router-view v-if="isRouterAlive"/>
</div>
export default {
name: 'App',
provide() {
return {
reload: this.reload//调用reload方法
}
},
data() {
return {
isRouterAlive: true//一开始router-view为true
}
},
methods: {
reload() {
this.isRouterAlive = false
//在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM
this.$nextTick(() => {
this.isRouterAlive = true
})
}
}
}
##### 12.2在页面操作
export default {
name: 'newproduct',
inject:['reload'],//在export default下面加上这一段
method:{
//调用App.vue下的this.reload()方法,来改变v-if的状态
clickDiv(){//刷新按钮调用的方法
this.reload()
}
}
[参考文档:如何实现 vue 中不跳转不闪动页面刷新?provide /inject 完美解决方案](https://segmentfault.com/a/1190000018875444)
## 十三、vue动态绑定 class
##### 13.1 对象方法
:class="{ 'active': isActive }"
#### 13.2 判断是否绑定一个active
:class="{'active':isActive==-1}"
或者
:class="{'active':isActive==index}"
##### 13.3绑定并判断多个
###### 第一种(用逗号隔开)
:class="{ 'active': isActive, 'sort': isSort }"
###### 第二种(放在 data 里面)
:class="classObject"
data() {
return {
classObject: {
active: true,
sort: false
}
}
}
###### 第三种(使用 computed 属性)
:class="classObject"
data() {
return {
isActive: true,
isSort: false
}
},
computed: {
classObject: function() {
return {
active: this.isActive,
sort: this.isSort
}
}
}
##### 13.4数组方法
###### 1.单纯数组
:class="[isActive,isSort]"
data() {
return {
isActive: 'active',
isSort: 'sort'
}
}
数组与三元运算符结合判断选择需要的class
三元运算符后面的“:”两边的class需要加上单引号
:class="[isActive?'active':'']"
或者
:class="[isActive==1?'active':'']"
或者
:class="[isActive==index?'active':'']"
或者
:class="[isActive==index?'active':'otherActiveClass']"
##### 13.5 数组对象结合动态判断
//前面这个 active 在对象里面可以不加单引号,后面这个 sort 要加单引号
:class="[{ active: isActive }, 'sort']"
或者
:class="[{ active: isActive==1 }, 'sort']"
或者
:class="[{ active: isActive==index }, 'sort']"
应用于组件
如果直接在自定义组件中使用 class 或 :class,那么样式规则就会直接应在这个组件的根元素上。
Vue.component('text-component', {
template: '<p class="content">不懂基因测序的学霸不是好的人工智能公司 CEO</p>'
});
var app = new Vue({
el: '#app',
data: {
isStrong: true
}
});
[参考文档01](https://www.jianshu.com/p/db4c7e7b7f2c)
[参考文档02](https://www.jianshu.com/p/45dbac5035e2)
### style三元表达式
动态绑定样式
[参考文档](https://www.cnblogs.com/art-poet/p/12036518.html)
也可以使用 v-bind:style 或 :style 直接给 HTML 元素绑定样式,它也有对应的对象语法与数组语法。
var app = new Vue({
el: '#app',
data: {
border:{
border:'1px solid #00F',
textShadow:'0 0 .3em gray'
}
}
});
因为 JS 属性不支持短横分隔命名,所以我们这里使用 CSS 也支持的驼峰命名法。
[参考文档](https://www.jianshu.com/p/db4c7e7b7f2c)
### 计算属性computed
##### 例:反转字符串:
原始字符串: {{ message }}
计算后反转字符串: {{ reversedMessage }}
message: 'Runoob!'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
我们可以使用 methods 来替代 computed,效果上两个都是一样的,但是 computed 是基于它的依赖缓存,只有相关依赖发生改变时才会重新取值。而使用 methods ,在重新渲染的时候,函数总会重新调用执行。。换句话说,computed 是局部渲染,而 methods 是全部渲染
区别:
- 1.methods是个方法,比如你点击事件要执行一个方法,这时候就用methods,
- 2.computed是计算属性,实时响应的,比如你要根据data里一个值随时变化做出一些处理,就用computed。
- 3.我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
- 4.methods必须需要一定的条件去触发,而computed则不需要.
- 5.computed依赖缓存,如果不需要经常变动的用computed,需要经常变动的用methods。如果你需要传参数,就用methods。
<br>
### computed
computed 属性默认只有 getter ,不过在需要时你也可以提供一个 setter :
var vm = new Vue({
el: '#app',
data: {
name: 'Google',
url: 'http://www.google.com'
},
computed: {
site: {
// getter
get: function() {
return this.name + ' ' + this.url
},
// setter
set: function(newValue) {
var names = newValue.split(' ')
this.name = names[0]
this.url = names[names.length - 1]
}
}
}
})
// 调用 setter, vm.name 和 vm.url 也会被对应更新
vm.site = 'http://www.runoob.com';
document.write('name: ' + vm.name);
document.write('
');
document.write('url: ' + vm.url);
从实例运行结果看在运行 vm.site = 'http://www.runoob.com'; 时,setter 会被调用, vm.name 和 vm.url 也会被对应更新。
## 十四、style样式绑定
class 与 style 是 HTML 元素的属性,用于设置元素的样式,我们可以用 v-bind 来设置样式属性。
Vue.js v-bind 在处理 class 和 style 时, 专门增强了它。表达式的结果类型除了字符串之外,还可以是对象或数组。
##### 14.1 class 属性绑定
我们可以为 v-bind:class 设置一个对象,从而动态的切换 class:
width: 100px;
height: 100px;
background: green;
}
isActive: true
}
})
例:text-danger 类背景颜色覆盖了 active 类的背景色:
##### 方法一:在对象中传入更多属性用来动态切换多个 class 。
width: 100px;
height: 100px;
background: green;
}
.text-danger {
background: red;
}
isActive: true,
hasError: true
}
})
##### 方法二:直接绑定数据里的一个对象:
width: 100px;
height: 100px;
background: green;
}
.text-danger {
background: red;
}
classObject: {
active: true,
'text-danger': true
}
}
})
##### 方法三:绑定返回对象的计算属性:
width: 100px;
height: 100px;
background: green;
}
.text-danger {
background: red;
}
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal',
}
}
}
})
##### 方法四:数组语法
width: 100px;
height: 100px;
background: green;
}
.text-danger {
background: red;
}
activeClass: 'active',
errorClass: 'text-danger'
}
})
还可以使用三元表达式来切换列表中的 class :
errorClass 是始终存在的,isActive 为 true 时添加 activeClass 类:
width: 100px;
height: 100px;
background: red;
}
.active {
width: 100px;
height: 100px;
background: green;
}
isActive: true,
activeClass: 'active',
errorClass: 'text-danger'
}
})
### style样式邦定
v-bind:style直接设置样式:
activeColor: 'green',
fontSize: 30
}
})
也可以直接绑定到一个样式对象:
styleObject: {
color: 'green',
fontSize: '30px'
}
}
})
v-bind:style 可以使用数组将多个样式对象应用到一个元素上:
baseStyles: {
color: 'green',
fontSize: '30px'
},
overridingStyles: {
'font-weight': 'bold'
}
}
})
[参考文档](https://www.jianshu.com/p/833b0b414c1a)
## 十五、vue组件参数传递:
Vue 组件间通信包括:父子组件间通信,兄弟组件间通信以及模块之间通信等。Vue 是数据驱动视图更新的框架, 所以对于 Vue 来说组件间的数据通信非常重要。Vue 实现组件间通信有很多方式,今天我来给大家讲解一下父子组件间通信:props 和\$emit。
### 15.1.子组件向父组件传递参数;
![image](https://upload-images.jianshu.io/upload_images/13341275-58ae8e71fd60a777.png?imageMogr2/auto-orient/strip|imageView2/2/w/681)
### 子组件 A:
<template>
<div class="childA-wrapper">
子组件A
</div>
</template>
<script>
export default {
data() {
return {
childA: '我是组件A传过来的值'
}
},
created: function() {},
mounted() {
this.sendDataParent()
},
methods: {
sendDataParent() {
// getChildDataA是在父组件on监听的方法
// 第二个参数this.childA是需要传的值
this.$emit('getChildDataA', this.childA)
}
}
}
</script>
### 子组件 B:
return {
childB:'我是组件B传过来的值'
}
},
created:function() {
},
mounted(){
this.sendDataParent()
},
methods: {
sendDataParent() {
// getChildDataB是在父组件on监听的方法
// 第二个参数this.childB是需要传的值
this.$emit('getChildDataB', this.childB)
}
}
}
### 父组件:
<div>
<v-childA v-on:getChildDataA="getChildDataA"></v-childA>
<v-childB v-on:getChildDataB="getChildDataB"></v-childB>
<div>获取组件A传过来的值:{{childAValue}}</div>
<div>获取组件B传过来的值:{{childBValue}}</div>
</div>
</template>
<script>
import childA from '@/components/childA.vue'
import childB from '@/components/childB.vue'
export default {
data() {
return {
childAValue:'',
childBValue:'',
}
},
methods: {
getChildDataA(childA){
console.log(childA)
this.childAValue=childA
},
getChildDataB(childB){
console.log(childB)
this.childBValue=childB
}
},
components: { 'v-childA': childA, 'v-childB': childB}
}
</script>
### 15.2、父组件向子组件传递参数
![image](https://upload-images.jianshu.io/upload_images/13341275-5b51bd3e9fc58653.png?imageMogr2/auto-orient/strip|imageView2/2/w/364)
##### 父组件:
<div>
<v-childA></v-childA>
<v-childB :sendBData="sendB"></v-childB>
</div>
</template>
<script>
import childA from '@/components/childA.vue'
import childB from '@/components/childB.vue'
export default {
data() {
return {
sendB:'父组件向B组件传递的参数'
}
},
methods: {
},
components: { 'v-childA': childA, 'v-childB': childB}
}
</script>
##### 子组件 B:
return {}
},
created: function() {},
mounted() {},
methods: {},
props: {
sendBData: String,
required: true
}
}
### 15.3、非父子组件进行传值;
![image](https://upload-images.jianshu.io/upload_images/13341275-8b197a7296e397ed.png?imageMogr2/auto-orient/strip|imageView2/2/w/264)
##### bus.js
import Vue from 'vue'
export default new Vue()
##### 组件 childB:
<div class="childB-wrapper">
</div>
data() {
return {
childB: '我是组件B的内容'
}
},
created: function() {},
mounted() {
this.elementByValue()
},
methods: {
elementByValue: function () {
Bus.$emit('val', this.childB)
}
}
}
组件 childA:
<div class="childA-wrapper">
A组件:<span>{{childB}}</span>
</div>
</template>
<script>
import Bus from '@/common/bus.js'
export default {
data() {
return {
childB: ''
}
},
created: function() {},
mounted() {
var that = this
// 用 $on事件来接收参数
Bus.$on('val', (data) => {
console.log(data)
that.childB = data
})
}
}
</script>
更多请查看:[你不知道的vue组件传值方式](https://juejin.im/post/5dafc84a6fb9a04de04d98d5)
### 16.vue脚手架配置预渲染,prerender-spa-plugin 配置
用到插件:cnpm install prerender-spa-plugin --save
脚手架 2.0:(自己的是 2.0)
##### 16.1 build/webpack.prod.conf.js 配置
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
const webpackConfig = merge(baseWebpackConfig, {
plugins: [
// vue-cli 生成的配置就有了
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency'
}),
// 配置 PrerenderSPAPlugin
new PrerenderSPAPlugin({
// 生成文件的路径,也可以与 webpakc 打包的一致。
staticDir: path.join(\_\ _dirname, '../dist'),
// 对应自己的所有路由文件,比如 index 有参数,就需要写成 /index/param1。这个其实不需要;千万不要加'/'这个 嵌套路由得 commonquestion 直接写即可
routes: ['index', '...', '/commonQuestion', '/commonQuestion/questionList', '/commonQuestion/questionDetailInfo'],
// ;
renderer: new Renderer({
inject: { // 可选;最好还是用
foo: 'bar'
},
headless: false, // 可选;最好用
renderAfterTime: 5000, // 通过实践是必选 官网说可选有误 一定要必选
renderAfterDocumentEvent: 'render-event' // 可选;最好用
})
}),
]
})
##### 16.2.路由得 index.js 添加属性:
mode:‘history’,
修改config/index.js 中的build的 assetsPublicPath: ‘/’ ;不然会导致刷新页面路径错乱导致样式或者js丢失;
修改main.js
new Vue({
el: '#app',
router,
store, // 如果需要了切记引入啊 切记需要挂载的全部挂载上去
render: h => h(App),
mounted () {
document.dispatchEvent(new Event('render-event'))
}
})
##### 16.3.build/webpack.prod.conf.js 配置
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
const path = require('path');
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV !== 'production') return;
return {
plugins: [
new PrerenderSPAPlugin({
// 生成文件的路径,也可以与 webpakc 打包的一致。
// 下面这句话非常重要!!!
// 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
staticDir: path.join(\_\ _dirname, 'dist'),
// 对应自己的路由文件,比如 a 有参数,就需要写成 /a/param1。
routes: ['/', '/product', '/about'],
// 这个很重要,如果没有配置这段,也不会进行预编译
renderer: new Renderer({
inject: {
foo: 'bar'
},
headless: false,
renderAfterTime: 5000, // 必选哈
// 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
renderAfterDocumentEvent: 'render-event'
})
}),
],
};
}
}
##### 16.4 main.js 配置
new Vue({
router,
store,
render: h => h(App),
mounted() {
document.dispatchEvent(new Event('render-event'))
}
}).$mount('#app')
其他修改同 2.0;
[参考文档](https://blog.csdn.net/Angely_cc/article/details/90145981)
### 十六、vue SPA单页面的 SEO 优化
vue-meta-info
官方地址: monkeyWangs/vue-meta-info
##### 1.安装
npm install vue-meta-info --save
##### 2.全局引入 vue-meta-info
import Vue from 'vue'
import MetaInfo from 'vue-meta-info'
Vue.use(MetaInfo)
##### 3.组件内静态使用 metaInfo
...
metaInfo: {
title: 'My Example App', // set a title
meta: [{ // set meta
name: 'keyWords',
content: 'My Example App'
}]
link: [{ // set link
rel: 'asstes',
href: 'https://assets-cdn.github.com/'
}]
}
}
##### 4.如果你的 title 或者 meta 是异步加载的,那么你可能需要这样使用
...
name: 'async',
metaInfo () {
return {
title: this.pageName
}
},
data () {
return {
pageName: 'loading'
}
},
mounted () {
setTimeout(() => {
this.pageName = 'async'
}, 2000)
}
}
##### 5.meta标签共有两个属性,分别是http-equiv属性和name属性
1. name 属性
name 属性主要用于描述网页,比如网页的关键词,叙述等。与之对应的属性值为 content,content 中的内容是对 name 填入类型的具体描述,便于搜索引擎抓取。
meta 标签中 name 属性语法格式是:
其中 name 属性共有以下几种参数。(A-C 为常用属性)
##### A. keywords(关键字)
说明:用于告诉搜索引擎,你网页的关键字。
##### B. description(网站内容的描述)
说明:用于告诉搜索引擎,你网站的主要内容。
##### C.viewport(移动端的视口)
说明:这个属性常用于设计移动端网页。
举例
##### D. robots(定义搜索引擎爬虫的索引方式)
说明:robots 用来告诉爬虫哪些页面需要索引,哪些页面不需要索引。
content 的参数有 all,none,index,noindex,follow,nofollow。默认是 all。
举例:
具体参数如下:
1.none : 搜索引擎将忽略此网页,等价于 noindex,nofollow。
2.noindex : 搜索引擎不索引此网页。
3.nofollow: 搜索引擎不继续通过此网页的链接索引搜索其它的网页。
4.all : 搜索引擎将索引此网页与继续通过此网页的链接索引,等价于 index,follow。
5.index : 搜索引擎索引此网页。
6.follow : 搜索引擎继续通过此网页的链接索引搜索其它的网页。
##### E. author(作者)
说明:用于标注网页作者
举例:
##### F. generator(网页制作软件)
说明:用于标明网页是什么软件做的
举例: (不知道能不能这样写):
##### G. copyright(版权)
说明:用于标注版权信息
举例:
//代表该网站为Lxxyx个人版权所有。
##### H. revisit-after(搜索引擎爬虫重访时间)
说明:如果页面不是经常更新,为了减轻搜索引擎爬虫对服务器带来的压力,可以设置一个爬虫的重访时间。如果重访时间过短,爬虫将按它们定义的默认时间来访问。
举例:
##### I. renderer(双核浏览器渲染方式)
说明:renderer 是为双核浏览器准备的,用于指定双核浏览器默认以何种方式渲染页面。比如说 360 浏览器。
举例:
//默认webkit内核
//默认IE兼容模式
//默认IE标准模式
##### 6. http-equiv 属性
http-equiv 相当于 HTTP 的作用,比如说定义些 HTTP 参数啥的。
meta 标签中 http-equiv 属性语法格式是:
其中 http-equiv 属性主要有以下几种参数:
##### A. content-Type(设定网页字符集)(推荐使用 HTML5 的方式)
说明:用于设定网页字符集,便于浏览器解析与渲染页面
举例:
//旧的HTML,不推荐
//HTML5设定网页字符集的方式,推荐使用UTF-8
##### B. X-UA-Compatible(浏览器采取何种版本渲染当前页面)
说明:用于告知浏览器以何种版本来渲染页面。(一般都设置为最新模式,在各大框架中这个设置也很常见。)
举例:
//指定IE和Chrome使用最新版本渲染当前页面
##### C. cache-control(指定请求和响应遵循的缓存机制)
用法 1.
说明:指导浏览器如何缓存某个响应以及缓存多长时间。这一段内容我在网上找了很久,但都没有找到满意的。
最后终于在 Google Developers 中发现了我想要的答案。
举例:
共有以下几种用法:
- no-cache: 先发送请求,与服务器确认该资源是否被更改,如果未被更改,则使用缓存。
- no-store: 不允许缓存,每次都要去服务器上,下载完整的响应。(安全措施)
- public : 缓存所有响应,但并非必须。因为 max-age 也可以做到相同效果
- private : 只为单个用户缓存,因此不允许任何中继进行缓存。(比如说 CDN 就不允许缓存 private 的响应)
- maxage : 表示当前请求开始,该响应在多久内能被缓存和重用,而不去服务器重新请求。例如:max-age=60 表示响应可以再缓存和重用 60 秒。
参考链接:HTTP 缓存
用法 2.(禁止百度自动转码)
说明:用于禁止当前页面在移动端浏览时,被百度自动转码。虽然百度的本意是好的,但是转码效果很多时候却不尽人意。所以可以在 head 中加入例子中的那句话,就可以避免百度自动转码了。
举例:
##### D. expires(网页到期时间)
说明:用于设定网页的到期时间,过期后网页必须到服务器上重新传输。
举例:
##### E. refresh(自动刷新并指向某页面)
说明:网页将在设定的时间内,自动刷新并调向设定的网址。
举例:
//意思是2秒后跳转向我的博客
##### F. Set-Cookie(cookie 设定)
说明:如果网页过期。那么这个网页存在本地的 cookies 也会被自动删除。
//格式
//具体范例