(获取本节完整代码 GitHub/chizijijiadami/practice1.1)
今天我们直接用入门教程的代码https://github.com/chizijijiadami/hand-to-hand来码一个企业官网。
0、写在前面
相关说明:这里相比入门教程 入门基础开发—手把手教你用vue开发,增加了css预处理器stylus( stylus官网 )、vuex状态管理( vuex官网 )、页面预渲染Prerendering( vue服务端渲染官网 ),并且结合了 vue-meta-info 达成网站搜索排名SEO优化的目的。其实这个项目里vuex不必要使用,面包屑导航可以用本地存储localStorage去写,只是为了后面的教程我们提前在这里提一下。
企业名称:大米工厂
页面模块:首页、旗下产品、新闻动态、关于我们、联系我们、招才纳贤
页面结构:
1、编写页面组件
拿到入门教程代码运行之前,我们修改一下package.json文件,这样运行项目后会自动打开浏览器访问。
"scripts": {
- "serve": "vue-cli-service serve --mode development",
- "serve-pro": "vue-cli-service serve --mode production ",
- "serve-test": "vue-cli-service serve --mode testing ",
+ "serve": "vue-cli-service serve --mode development --open",
+ "serve-pro": "vue-cli-service serve --mode production --open",
+ "serve-test": "vue-cli-service serve --mode testing --open",
"build": "vue-cli-service build --mode production",
"build-dev": "vue-cli-service build --mode development",
"build-test": "vue-cli-service build --mode testing",
"lint": "vue-cli-service lint"
},
● 添加头部底部组件
src>components>Header.vue
<template>
<header>
<div>
<!--自己找张图片做logo-->
<router-link class="logo" to="/index"><img src="../assets/images/logo.png" /></router-link>
<nav>
<router-link to="/index" active-class="actived">首页</router-link>
<router-link to="/products" active-class="actived">产品</router-link>
<router-link to="/news" active-class="actived">动态</router-link>
<router-link to="/about" active-class="actived">关于我们</router-link>
<router-link to="/contact" active-class="actived">联系我们</router-link>
<router-link to="/recruit" active-class="actived">招贤纳士</router-link>
</nav>
</div>
</header>
</template>
src>components>Footer.vue
<template>
<footer class="content-center">大米工厂</footer>
</template>
● 添加各页面, 删掉pages/List文件以及子文件
src>pages>Products>index.vue
<template>
<div class="content-center">Products</div>
</template>
其他页面内容如Products/index.vue,只是将 div 标签中的 Product 改为相应页面名,方便识别,完成后,pages文件结构如下图:● 给新加页面添加路由
src>router>index.js
- {
- path: '/list',
- component: _import('List/index'),
- children: [
- {
- path: 'detail',
- component: _import('List/Detail/index')
- },
- {
- path: 'feature',
- component: _import('List/Feature/index')
- }
- ]
- },
+ {
+ path: '/products',
+ component: _import('Products/index')
+ }, {
+ path: '/news',
+ component: _import('News/index')
+ }, {
+ path: '/about',
+ component: _import('About/index')
+ }, {
+ path: '/contact',
+ component: _import('Contact/index')
+ }, {
+ path: '/recruit',
+ component: _import('Recruit/index')
+ }
另外将路由的模式改为 history
export default new Router({
+ mode:'history',
routes: [
......
]
删掉文件src/components/Helloworld.vue,改写src/App.vue
src/App.vue
<template>
<div id="app">
- <header>
- <nav><router-link to="/index">index</router-link><router-link to="/list">list</router-link></nav>
- </header>
- <router-view></router-view>
+ <Header />
+ <div class="content">
+ <router-view></router-view>
+ </div>
+ <Footer />
</div>
</template>
<script>
+ import Header from "./components/Header";
+ import Footer from "./components/Footer";
export default {
name: "App",
+ components: {
+ Header,
+ Footer
+ }
};
</script>
到此,浏览器里可以看到网站很丑的样子,下面我们就给网站加样式。
2、结合stylus加样式 stylus官网
这里在入门篇中提到过,可以减少css代码的编写等。
● 安装代码
yarn add stylus stylus-loader -D //-D是因为只在开发的时候用到
● 给vs code添加stylus格式扩展,搜索stylus,安装language-stylus,另外这里为了阻止按【Shift+Alt+F】自动化格式代码后,css样式自动加上大括号等符号,我们添加Manta's Stylus Supremacy。找到如下截图的三个,去掉前面的勾勾,按【Ctrl+s】保存一下就可以了,如果没生效,可以先看看有没有保存好,如果是保存了的仍然没有生效,软件重启大法试一下。
● 分别添加头部、底部和中间内容最外部div的样式
src>components>Header.vue
+ <style lang="stylus" scoped>
+ header
+ width 100%
+ background-color #000000
+ height 72px
+ div
+ display flex
+ width 1200px
+ margin 0 auto
+ .logo
+ display inline-block
+ height 72px
+ nav a, nav a:visited
+ display inline-block
+ width 100px
+ text-align center
+ color white
+ font-size 16px
+ line-height 72px
+ a:hover, a.actived
+ background-color #444
+ </style>
src>components>Footer.vue
+ <style lang="stylus" scoped>
+ footer
+ text-align center
+ line-height 60px
+ </style>
src>App.vue
+ <style lang="stylus" scoped>
+ .content
+ min-height calc(100vh - 72px - 60px)
//给中间内容一个最小高宽撑起页面会好看一点
+ </style>
删掉src/assets/styles/style.css,新建src/assets/styles/style.styl
a,a:active,a:hover,a:visited
text-decoration none
.content-center
width: 1200px
margin 0 auto
修改main.js文件的引入
import './assets/styles/reset.css'
- import './assets/styles/style.css'
+ import './assets/styles/style.styl'
到这里我们一个简单的企业官网开发框架就搭起来了,不过vue开发的SPA应用不利于SEO,因为只是比较简单的几个信息也,我们用预渲染的方式去解决,服务端渲染会在下个实践1.2里实现。
3、prerender-spa-plugin 实现预渲染
● 安装 prerender-spa-plugin
yarn add prerender-spa-plugin -D
● 修改 vue.config.js
+ const PrerenderSPAPlugin = require('prerender-spa-plugin');
+ const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
const path = require('path')
const resolve = dir => path.resolve(__dirname, dir)
module.exports = {
publicPath: './',
chainWebpack: config => {
config.resolve.alias
.set("@", resolve('src'))
},
+ configureWebpack: config => {
+ if (process.env.NODE_ENV !== 'production') return;
+ return {
+ plugins: [
+ new PrerenderSPAPlugin({
+ staticDir: path.join(__dirname, 'dist'),
+ routes: ['/index', '/products', '/news', '/about', '/contact', '/recruit'], //需要预渲染的路由名称
+ renderer: new Renderer({
+ inject: {
+ foo: 'bar'
+ },
+ headless: false, //打包时浏览器进行渲染自动检测用的,false代表使用
+ renderAfterDocumentEvent: 'render-event' //挂载后激发的事件名称,在main.js中使用
+ })
+ }),
+ ],
+ };
+ }
}
修改 main.js
new Vue({
router,
render: h => h(App),
+ mounted() {
+ document.dispatchEvent(new Event('render-event'));
+ }
}).$mount('#app')
● 打包部署
yarn build
会发现有一闪而过的窗口,那是SEO自动测试可以忽略。成功后dist目录如下:原来是我们忽略了路径问题,由于参与预渲染的页面在dist中都单独生成了独立的文件夹,里面的页面引入文件时应该是以 ../ 开头,而不是我们设置的 publicPath:'./'。但是如果把 publicPath:'./',改成 publicPath:'../'重新启动程序,就会发现本地开发路径又不对了,如图多了两点
这时候我们的环境文件就该上场了,还记得.env.development和.env.production两个文件么,修改它们。
.env.development
NODE_ENV = 'development'
VUE_APP_BASE_API = 'http://192.168.0.100'
+ VUE_APP_PUBLIC_PATH = './'
.env.production
NODE_ENV = 'production'
VUE_APP_BASE_API = 'http://192.168.0.100'
+ VUE_APP_PUBLIC_PATH = '../'
vue.config.js
- publicPath:'../',
+ publicPath: process.env.VUE_APP_PUBLIC_PATH,
重新启动程序,本地开发访问正常,接着试下重新打包部署,可以看到也OK了。但是浏览器标签里的标题却没有变化,这就要用到 vue-meta-info 了。4、结合 vue-meta-info 进行SEO优化
● 安装
yarn add vue-meta-info -D
● 使用
修改src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
+ import MetaInfo from 'vue-meta-info'
Vue.config.productionTip = false
+ Vue.use(MetaInfo)
由于页面比较多,我们对meta信息进行统一管理,新建src/common/util/metaInfo.js文件
src/common/util/metaInfo.js
export default {
Index: {
title: "首页-大米工厂",
meta: [{
name: 'keyWords',
content: 'content'
}, {
name:'description',
content: 'content',
}]
},
Products:{
title: "产品-大米工厂",
meta: [{
name: 'keyWords',
content: 'content'
}, {
name:'description',
content: 'content',
}]
},
News:{
title: "新闻动态-大米工厂",
meta: [{
name: 'keyWords',
content: 'content'
}, {
name:'description',
content: 'content',
}]
},
About:{
title: "关于我们-大米工厂",
meta: [{
name: 'keyWords',
content: 'content'
}, {
name:'description',
content: 'content',
}]
},
Contact:{
title: "联系我们-大米工厂",
meta: [{
name: 'keyWords',
content: 'content'
}, {
name:'description',
content: 'content',
}]
},
Recruit:{
title: "招贤纳士-大米工厂",
meta: [{
name: 'keyWords',
content: 'content'
}, {
name:'description',
content: 'content',
}]
}
}
修改src/pages/Index/index.vue
<script>
+ import metaInfo from '@/common/util/metaInfo'
export default {
name:"IndexIndex",
- created(){ //这个输出没用了,我们给删掉
- console.log(process.env.VUE_APP_BASE_API,'输出VUE_APP_BASE_API');
- },
+ metaInfo:metaInfo.Index,
}
</script>
修改src/pages/Products/index.vue
+ <script>
+ import metaInfo from '@/common/util/metaInfo'
+ export default {
+ metaInfo:metaInfo.Products,
+ }
</script>
其他页面依次修改引入使用。改完以后自己点点看观察浏览器标签里标题变化,再打包部署一下试试。
5、结合 vuex 写面包屑导航
在使用之前我们先做一些准备工作,给 关于我们 添加两个子路由。
新建 src>pages>About>Feature>index.vue
<template>
<div>about-feature</div>
</template>
<style lang='stylus' scoped>
div
background-color #e68cab
</style>
新建 src>pages>About>Team>index.vue
<template>
<div>about-team</div>
</template>
<style lang='stylus' scoped>
div
background-color #87bf8d
</style>
修改src>pages>About>index.vue
<template>
- <div class="content-center">About</div>
+ <div class="content-center">
+ <p>About</p>
+ <p>
+ <router-link to="/about/feature">Feature</router-link><router-link to="/about/team">Team</router-link>
+ </p>
+ <router-view></router-view>
+ </div>
</template>
<script>
import metaInfo from "@/common/util/metaInfo";
export default {
metaInfo: metaInfo.About
};
</script>
+ <style lang='stylus' scoped>
+ div p a
+ padding 15px
+ </style>
修改src>router>index.js,添加访问路径,并且给每个路由配上meta信息,用于面包屑的显示,这里只贴了关于我们的,其余的自己加一下,metaInfo.js里的自己加加看。
{
path: '/about',
component: _import('About/index'),
+ meta: {
+ title: "关于我们"
+ },
+ children: [
+ {
+ path: 'feature',
+ component: _import('About/Feature/index'),
+ meta: {
+ title: "特点"
+ }
+ },
+ {
+ path: 'team',
+ component: _import('About/Team/index'),
+ meta: {
+ title: "团队"
+ }
+ }
+ ]
}
到这里可以看到about页面可以切换显示新添加的两个页面了,下面我们正式开始。
安装
yarn add vuex
使用
新建 src>data>store>index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
hasCrumbs: true, // 是否需要面包屑导航
indexPage: [ // 面包屑导航首页
{
path: '/index',
meta: {
title: '首页',
}
}
],
crumbsList: [] //面包屑导航数组
},
mutations: {
SET_CRUMBS(state, list) { //设置面包屑导航数组
if (list.length == 1 && list[0].path == '/index') {
state.crumbsList = []
} else {
state.crumbsList = state.indexPage.concat(list)
}
}
},
actions: {
setCrumbs({ commit }, list) { //分发设置面包屑导航数组
commit("SET_CRUMBS", list)
}
}
})
export default store
修改main.js,引入状态文件
import Vue from 'vue'
import App from './App.vue'
import router from './router'
+ import store from './data/store'
import MetaInfo from 'vue-meta-info'
Vue.config.productionTip = false
Vue.use(MetaInfo)
//样式
import './assets/styles/reset.css'
import './assets/styles/style.styl'
new Vue({
router,
+ store,
render: h => h(App),
mounted() {
document.dispatchEvent(new Event('render-event'));
}
}).$mount('#app')
新建面包屑导航组件 src>components>Crumbs.vue,在页面中使用
<template>
<div class="content-center crumbs">
<!-- 下面的三目运算主要针对最后一个路由,它不需要被点击,后面也不需要 > 符号-->
<a
v-for="(item,index) in getCrumbs"
:class="index!=getCrumbs.length-1?'':'crumb-end'"
:key="index"
:href="index!=getCrumbs.length-1?item.path:'javascript:void(0)'"
>
<!-- item.meta.title中的meta就是我们在路由文件router/index.js中设置的 -->
{{item.meta.title}}
<span>{{index!=getCrumbs.length-1?" > ":""}}</span>
</a>
</div>
</template>
<script>
export default {
computed: {
getCrumbs() {
return this.$store.state.crumbsList;
}
}
};
</script>
<style lang='stylus' scoped>
.crumbs
line-height 30px
font-size 14px
.crumbs a
color #2973b7
.crumbs span
color #333
.crumbs .crumb-end
color #333
cursor default
</style>
修改App.vue,引入使用面包屑导航
<template>
<div id="app">
<Header />
+ <!-- getCrumbs.length为0时,不显示面包屑导航 -->
+ <Crumbs v-show="getHasCrumbs&&getCrumbs.length" />
+ <!-- 有面包屑导航的时候添加class content-has-crumbs是为了控制有无面包屑导航时的页面最小高度 -->
<div class="content" :class="{'content-has-crumbs':getHasCrumbs&&getCrumbs.length}">
<router-view></router-view>
</div>
<Footer />
</div>
</template>
<script>
import Header from "./components/Header";
import Footer from "./components/Footer";
+ import Crumbs from "./components/Crumbs";
export default {
name: "App",
components: {
Header,
Footer,
+ Crumbs
},
+ computed: {
+ getHasCrumbs() {
+ return this.$store.state.hasCrumbs;
+ },
+ getCrumbs() {
+ return this.$store.state.crumbsList;
+ }
+ }
};
</script>
<style lang="stylus" scoped>
.content
min-height calc(100vh - 72px - 60px)
+ .content-has-crumbs
+ min-height calc(100vh - 72px - 60px - 30px)
</style>
利用全局导航守卫 router.afterEach 将 路由属性$route.matched 传入面包屑数组 state.crumbsList 中。
新建scr>common>guards>index.js
import router from '@/router'
import store from '@/data/store'
router.afterEach(to => {
store.dispatch('setCrumbs', to.matched) //状态文件actions里面定义的方法
})
修改main.js
//样式
import './assets/styles/reset.css'
import './assets/styles/style.styl'
+ //导航守卫
+ import './common/guards'
现在点击页面看看就有面包屑导航了,如果整个项目都不需要,把 state.hasCrumbs 改为false 就好了。
稍微小结一下,vuex的初次使用就是(1)新建状态文件(2)main.js中引入(3)页面组件中使用。
6、补充说明
关于多级路由的预渲染,例如有个路径 list/detail ,那么在打包后detail页面的文件夹会在list文件夹里面,这样detail文件夹里面的页面引入的资源路径又是缺少了一层的,这里给出的解决方案是,专门写一个处理路径的js,在打包后运行。这里js做的处理应当是将属于那个子文件的资源移动到跟它同级的目录,同时这些资源需要在命名上配合。例如detail页面的资源名里都加上detail以区分。
这个js功能目前也没十分必要去用到,先留在这里,后面有时间就来补上具体代码。
感谢阅读,喜欢的话点个赞吧:)
更多内容请关注后续文章。。。
一、vue入门基础开发—手把手教你用vue开发
二、vue+ElementUI开发后台管理模板—布局
三、vue+ElementUI开发后台管理模板—功能、资源、全局组件
四、vue+ElementUI开发后台管理模板—方法指令、接口数据
五、vue+ElementUI开发后台管理模板—精确到按钮的权限控制