Vue-cms
1. App.vue 组件的基本设置
1.1 header
头部的固定导航栏使用
Mint-UI
的Header
组件;
- 按需导入模块
- 引用
1.2 底部Tabbar
-
底部的页签使用
mui
的tabbar
; -
先把 扩展图标的 css 样式和扩展字体库 ttf 文件,拷贝到 项目中
-
按需导入
// 引入Tabbar模块 import './lib/mui/css/mui.min.css' import './lib/mui/css/icons-extra.css' import './lib/mui/fonts/mui-icons-extra.ttf'
-
引用
- 将底部的页签,改造成
router-link
来实现单页面的切换; - 要在中间区域放置一个
router-view
来展示路由匹配到的组件
- 将底部的页签,改造成
-
设置路由
-
Tab Bar 路由激活时高亮
linkActiveClass: 'mui-active'
2. 底部页签的切换动画
- 将
router-view
用transition
包起来 - 设置动画
- 注意在
.v-leave-to
中设置position: absolute;
translate(-100%);
和translate(100%);
- 注意在
3. Home页
3.1 轮播图
使用 vue-resource获取数据
- 安装
npm install vue-source
- 引入
import Vueresource from 'vue-resource'
Vue.use(Vueresource)
Home.vue文件
- 遍历 :
v-for="item in LunboList" :key="item.id"
- img的src一定要加
:
==>:src
- 引入Toast :
import { Toast } from 'mint-ui'
- 将数据保存在data中
this.LunboList = result.body.message
3.2 六宫格
- 引入MUI中的grid(九宫格)代码
- 修改样式
4. 新闻资讯
4.1 改造路由链接
a
->router-link
to/home/newslist
- 创建vue组件
- 在router.js中引入组件
4.2 新闻列表页面
- 绘制页面
- 使用MUI中的media-list
- h1-> 14px p -> (手机端 12px最合适)
dispaly:flex
justify-content:space-between
- vue.resource获取数据
-
设置请求的根目录
Vue.http.options.root = 'http://www.liulongbin.top:3005';
-
发送请求
注意:Note that for the root option to work, the path of the request must be relative. This will use this the root option:
Vue.http.get('someUrl')
while this will not:Vue.http.get('/someUrl')
this.$http.get('api/getnewslist').then()
-
将数据存放到数组中
-
在created中调用
- 渲染页面
v-for
:key
:src
4.3 格式化时间
javaScript日期处理类库:Moment.js
- 安装
npm i moment
- 导入
import moment from 'moment'
- 在main中定义
全局
的过滤器- Vue.filter()
{{item.add_time | dateFormat }}
4.4 新闻详情页面
- 路由链接 (提供id)
- to="’/home/newsinfo/’+ item.id"(注意拼接)
{path:'/home/newsinfo/:id'}
- data = >
id : this.$router.params.id
- 绘制页面 -> 获取数据 -> 渲染页面
- 格式化时间
v-html
- 图片问题 : 去掉
scoped
5. 单独封装评论子组件
5.1 创建组件
-
导入
-
注册
components:{}
-
写入页面
-
样式
- button 幽灵: plain
5.2 获取数据
- 父向子传值
:id=this.id
props:["id"]
- 默认展示第一页
pageIndex:1
- 第
i+1
楼
5.3 加载更多
-
注册点击事件
-
pageIndex++
-
每当获取新评论数据的时候,不要把老数据清空覆盖,而是应该以老数据,拼接上新数据
```js ```
this.comments = this.comments.concat(result.body.message)
```
5.4 发表评论
-
文本框双向数据绑定
-
校验评论内容是否为空
-
发送请求
-
全局设置post的表单数据格式
-
this.$http.post( 'api/postcomment/'+this.id, {content:this.content}, {emulateJSON:true}) .then()
-
//Vue.http.options.emulateJSON = true; //全局设置MIME类型 this.$http.post( 'api/postcomment/'+this.id, {content:this.content}) .then()
-
-
拼接评论对象,用unshift添加到数组
-
6. 图片分享
6.1 顶部滑动条
-
使用MUI
-
去掉
mui-fullscreen
-
初始化
- 先导入 mui 的JS文件:
import mui from '../../../lib/mui/js/mui.min.js'
- 初始化
- 先导入 mui 的JS文件:
Bug1:严格模式问题
- 导入的mui.js会报错
Uncaught TypeError: ‘caller’, ‘callee’, and ‘arguments’ properties may not be accessed on strict mode functions or the arguments objects for calls to them
“调用者”、“被调用者”和“参数”属性可能不会在严格模式函数或调用它们的参数对象上被访问
- 原因: webpack打包好的bundle.js默认是启用严格模式的,mui.js中用到了"caller",“callee”,"arguments"的东西,两者冲突了
- 解决: 禁用webpack打包时候的严格模式
移出严格模式: babel-plugin-transform-remove-strict-mode
- 安装
npm install babel-plugin-transform-remove-strict-mode
- .babelrc文件设置:
"plugins": ["transform-remove-strict-mode"]
Bug2:无法滑动问题
tab-top-webview-main
组件第一次显示到页面中的时候,无法被滑动解决: 在 组件的
mounted
事件钩子中,注册 mui 的滚动事件:
mounted() {
// 需要在组件的 mounted 事件钩子中,注册 mui 的 scroll 滚动事件
mui('.mui-scroll-wrapper').scroll({
deceleration: 0.0005 //flick 减速系数,系数越大,滚动速度越慢,滚动距离越小,默认值0.0006
});
}
Bug3:滑动时报警告问题
- 报错
Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080
- 原因:(是chrome为了提高页面的滑动流畅度而新折腾出来的一个东西)
http://www.cnblogs.com/pearl07/p/6589114.html
https://developer.mozilla.org/zh-CN/docs/Web/CSS/touch-action
- 解决: 可以加上* { touch-action: pan-y; } 这句样式去掉。
Bug4:tabbar无法切换 问题
和 App.vue 中的
router-link
身上的类名mui-tab-item
存在兼容性问题,导致tab栏失效,可以把mui-tab-item
改名为mui-tab-item1
,并复制相关的类样式,来解决这个问题;
.mui-bar-tab .mui-tab-item1.mui-active {
color: #007aff;
}
.mui-bar-tab .mui-tab-item1 {
display: table-cell;
overflow: hidden;
width: 1%;
height: 50px;
text-align: center;
vertical-align: middle;
white-space: nowrap;
text-overflow: ellipsis;
color: #929292;
}
.mui-bar-tab .mui-tab-item1 .mui-icon {
top: 3px;
width: 24px;
height: 24px;
padding-top: 0;
padding-bottom: 0;
}
.mui-bar-tab .mui-tab-item1 .mui-icon~.mui-tab-label {
font-size: 11px;
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
获取图片分类列表
this.imgcategory.unshift(category)
:class="['mui-control-item', item.id == 0 ? 'mui-active':'']"
6.2 图片列表
图片懒加载
- mint-ui 中的 lazy-load
- 修改main.js文件
//main.js
import MintUI from 'mint-ui'
Vue.use(MintUI)
// mint-ui样式
import 'mint-ui/lib/style.css'
获取图片
- 默认加载分类id = 0的图片
created(){this.getimagesByCateId(0)}
- 点击时获取图片
@tap="getimagesByCateId(item.id)"
C3样式
- 3像素问题 :
vertical-align:"middle"
- 边框阴影:
box-shadow: 0 0 9px #999;
- 背景半透明
background:rgba(0,0,0,0.4)
- 统一图片描述高度:
max-height:84px;
6.3 图片详情
页面及路由
li->router-link tag="li"
:to="'/home/photolist/photoinfo/'+item.id"
插入评论组件
<comment :id=id></comment>
vue-preview
-
装包
npm i vue-preview@1.0.5
(老版本) -
img标签上的class不能去掉
-
每个图片数据中必须有w和h属性
this.thumimages.forEach(item=>{ item.w = 600, item.h = 400 }
7. 商品购买
7.1 商品列表
垂直均匀分布
-
display: flex;
-
flex-direction: column;
-
justify-content: space-between;
7.2 商品详情
####编程式导航
-
在网页中有两种跳转方式
- 标签跳转:使用a标签的形式
- 编程式导航: 使用window.location.href的形式
- vue.router 编程式导航
this.$router.push({ name: '组件名称', params: { userId }})
-
实现跳转
-
this.$router.push
方法godetail(id){ this.$router.push({name:'goodsinfo',params:{goodsid:id}}) }
-
路由
{path:'/home/goodslist/goodsinfo/:goodsid',component:goodsinfo,name:'goodsinfo'}
-
$route
与 $router
-
this.$route是路由参数对象,params,query等都属于它
-
this.$router是有个路由导航对象,可以使用JS代码实现路由的前进后退跳转
使用MUI卡片视图
-
抽离轮播图为组件
-
父向子传数据
-
:src问题解决:
result.body.message.forEach(item=>{ item.img = item.src })
-
Vue绑定class
:class="{'full':isFull}"
-
-
购买数量组件
-
单独封装一个子组件
-
使用mui–>numbox
-
初始化数字选择框钻组件
-
import mui from '../../lib/mui/js/mui.min.js' export default { mounted(){ //mounted是实例创建期间的最后一个生命周期函数 //已经渲染到页面上了 mui('.mui-numbox').numbox() } }
-
-
-
渲染数据
7.3 图文介绍
-
编程式导航跳转
goDesc(id){this.$router.push()}
-
图片问题
- 去掉scoped
width:100%
7.4 商品评论
组件中引用comment组件
7.5 小球动画
-
定位
-
半场动画,需要用钩子函数
- @before-enter=“beforeEnter” (el)
- @enter=“enter” (el,done)
- @after-enter=“afterEnter” (el)
小球优化思路
enter(el,done){ // 优化: // 1.计算小球初始位置 const ballPosition = this.$refs.ball.getBoundingClientRect() // console.log(ballPosition) //DOMRect {bottom: 279,height: 15,left: 150,right: 165,top: 264,width: 15,x: 150,y: 264} //2. 获取徽标在页面中的位置 const badgePosition = document.getElementById('badge').getBoundingClientRect() //3.计算距离 var movex = badgePosition.left - ballPosition.left var movey = badgePosition.top - ballPosition.top console.log(movex,movey) el.offsetWidth el.style.transition = 'all 1s cubic-bezier(.4,-0.3,1,.68)' el.style.transform = `translate(${movex}px,${movey}px)` done() }
7.6 加入购物车
设置最大值
-
父向子传最大值
-
通过watch属性监听max
watch:{ max : function (nVal,oVal) { //动态设置数字框最大值 mui('.mui-numbox').numbox().setOption('max',nVal) } }
父获取子的value值
-
父
- 向子传递方法
@numberVal="getnumber
- 存储子传过来的数据
getnumber(data){ this.numbox = data }
- 向子传递方法
-
子
-
为input框绑定事件
@change="changeCount"
-
向父传值
changeCount(){ var num = parseInt(this.$refs.number.value) this.$emit('numberVal',num) }
-
8. Vuex
8.1 步骤
vuex是为了保存组件之间共享数据而诞生的,是一个全局的共享数据存储区域,就相当于是一个数据的仓库
- props,data和vuex的区别
-
安装
npm i vuex
-
创建实例
import Vuex from 'vuex' Vue.use(Vuex) const store = new Vuex.Store({ state:{//存放数据 }, mutations:{ //mutations的函数列表中最多支持两个参数 //- 参数1:state状态 //- 参数2:通过commit提交过来的参数(可传一个对象) }, getters:{ //getters只负责对外提供数据,不负责修改数据 } })
- getters和computed和filters
- 过滤器和getters都没有修改原数据,都是把原数据做了一层包装,提供给了调用者
- getters和computed比较像,只要state中的数据发生了变化那么如果getter正好也引用了这个数据,那么就会立即出发getters的重新求职
- getters和computed和filters
-
将vuex实例挂载到vue实例上
8.2 总结
- state中的数据不能直接修改,若想修改必须通过mutations
- 若组件想要直接从state上获取数据,需要
this.$store.state.***
- mutations修改数据:
this.$store.commit('方法的名称',唯一的一个参数)
- getters:
this.$store.getters.***
9. 加入购物车
9.1 点击事件
-
点击事件
addToShopCar (){ var goodsinfo={id,count, price,selected} // 调用store-->mutations-->addInfoToCar this.$store.commit('addInfoToCar',goodsinfo) }
-
调用mutations里的addInfoToCar方法
addInfoToCar(state,goodsinfo){ // 将商品信息添加到car中 // 如果购物车中已有该商品则只需增加数量,否则添加整个信息 var flag = false //假设购物车中没有该商品 state.car.some(item => { if (item.id == goodsinfo.id) { item.count += parseInt(goodsinfo.count) flag = true return true } }) if(!flag) { state.car.push(goodsinfo) } }
9.2 徽标数值自动更新
-
getters
getCount(state){ var c = 0 state.car.forEach(item=>{ c += item.count }) return c }
-
调用
{{this.$store.getters.getCount}}
9.3 实现本地存储
-
addInfoToCar()
// 当更新car后,将car数组存储在localStorage中 localStorage.setItem('car',JSON.stringify(state.car))
-
获取car
// 获取localStorage中的car数组 var car = JSON.parse(localStorage.getItem('car') || '[]') const store = new Vuex.Store({ state:{ car:car }, })
10. 购物车商品列表
绘制页面
请求数据
初始化数量值
getGoodsCount(state) {
var o ={}
state.car.forEach(item=>{
o[item.id] = item.count
})
return o;
}
<numboxc :num="$store.getters.getGoodsCount[item.id]"></numboxc>
商品数量同步到store中
changeCt(){
// 将num同步到store中
this.$store.commit('updateCar',{
count : this.$refs.numbers.value,
id : this.goodsid
})
}
updateCar(state,info){
state.car.some(item=>{
if(item.id == info.id){
item.count = parseInt(info.count)
return true;
}
// return true; //记住啊,这个bug让你废了一天!
})
localStorage.setItem('car',JSON.stringify(state.car))
}
删除商品
delgoods(id,i){
// 列表删除
this.infolist.splice(i,1)
// store删除
this.$store.commit('delgoods',id)
}
//mutations
delgoods(state,id){
state.car.some((item,i)=>{
if(item.id == id){
state.car.splice(i,1)
return true
}
})
localStorage.setItem('car',JSON.stringify(state.car))
}
选中状态
1. store–>页面
<mt-switch v-model="$store.getters.getSelected[item.id]" @change="switchChange"></mt-switch>
//getters
getSelected(state){
var s = {}
state.car.forEach(item=>{
s[item.id] = item.selected
})
return s //返回的是一个对象
}
2. 页面–> store
<mt-switch
v-model="$store.getters.getSelected[item.id]"
@change="switchChange(item.id,$store.getters.getSelected[item.id])"></mt-switch>
switchChange(id,selected){
this.$store.commit('switchChange',{
id:id,
selected:selected
})
}
//mutations
switchChange(state,info){
state.car.forEach(item=>{
if(item.id == info.id) {
item.selected = info.selected
}
})
localStorage.setItem('car',JSON.stringify(state.car))
}
计算总价和数量
-
数量
//getters getSelectedCount(state){ var selectedCount = 0 state.car.forEach(item=>{ if(item.selected == true){ selectedCount += item.count } }) return selectedCount; }
-
总价
//getters getSumPrice(state){ var sum = 0 state.car.forEach(item=>{ if(item.selected == true){ sum += item.count * item.price } }) return sum; }
11 返回按钮
-
点击按钮–>编程式导航
this.$router.go(-1)
-
首页不显示返回
-
v-show="flag"
-
监听地址
$route.path
watch:{ "$route.path":function(nVal,oVal){ if(nVal == '/home') { this.flag = false } else { this.flag = true } } }
-
刷新判断是否是首页
created(){ this.flag = this.$route.path == '/home'? false : true }
-
12. 在手机上预览
- 保证手机和开发项目的电脑处于同一个WIFI环境中,也就是说手机可以访问到电脑的IP
- 打开项目中的package.json文件,在dev脚本中添加一个–host指令,把当前电脑的WIFI IP地址设置为–host的指令值
- 查看自己电脑所处的WIFI的IP
- 在CMD终端中运行ipconfig,查看无线网的IP地址
–host 192.168.43.202
13. 打包并托管
将项目托管到Apache并启用Gzip压缩
- 打包
- 先将根目录里的dist文件删除
- 运行
webpack
- 托管
- 将dist里边的index.html和bundle文件托管到apach中
- phpStudy–>其他选项菜单–>网站根目录
- 可以直接在
127.0.0.1:80
中打开
- 可以直接在
14. 开启Apache的gzip压缩
要让apache支持gzip功能,要用到deflate_Module和headers_Module。打开apache的配置文件httpd.conf,大约在105行左右,找到以下两行内容:(这两行不是连续在一起的)
#LoadModule deflate_module modules/mod_deflate.so
#LoadModule headers_module modules/mod_headers.so
然后将其前面的“#”注释删掉,表示开启gzip压缩功能。开启以后还需要进行相关配置。在httpd.conf文件的最后添加以下内容即可:
<IfModule deflate_module>
#必须的,就像一个开关一样,告诉apache对传输到浏览器的内容进行压缩
SetOutputFilter DEFLATE
DeflateCompressionLevel 9
</IfModule>
最少需要加上以上内容,才可以生gzip功能生效。由于没有做其它的额外配置,所以其它相关的配置均使用Apache的默认设置。这里说一下参数“DeflateCompressionLevel”,它表示压缩级别,值从1到9,值越大表示压缩的越厉害。