使用 饿了么的 MintUI 组件
[Github 仓储地址](https://github.com/ElemeFE/mint-ui)
[Mint-UI官方文档](http://mint-ui.github.io/#!/zh-cn)
1. 导入所有MintUI组件:
import MintUI from 'mint-ui'
2. 导入样式表:
import 'mint-ui/lib/style.css'
3. 在 vue 中使用 MintUI:
Vue.use(MintUI)
4. 使用的例子:
<mt-button type="primary" size="large">primary</mt-button>
使用 MUI 组件
[官网首页](http://dev.dcloud.net.cn/mui/)
[文档地址](http://dev.dcloud.net.cn/mui/ui/)
使用 MUI 代码片段
> 注意: MUI 不同于 Mint-UI,MUI只是开发出来的一套好用的代码片段,里面提供了配套的样式、配套的HTML代码段,类似于 Bootstrap; 而 Mint-UI,是真正的组件库,是使用 Vue 技术封装出来的 成套的组件,可以无缝的和 VUE项目进行集成开发;
> 因此,从体验上来说, Mint-UI体验更好,因为这是别人帮我们开发好的现成的Vue组件;
> 从体验上来说, MUI和Bootstrap类似;
> 理论上,任何项目都可以使用 MUI 或 Bootstrap,但是,MInt-UI只适用于Vue项目;
注意: MUI 并不能使用 npm 去下载,需要自己手动从 github 上,下载现成的包,自己解压出来,然后手动拷贝到项目中使用;
1. 导入 MUI 的样式表:
import '../lib/mui/css/mui.min.css'
2. 在`webpack.config.js`中添加新的loader规则:
{ test: /\.(png|jpg|gif|ttf)$/, use: 'url-loader' }
3. 根据官方提供的文档和example,尝试使用相关的组件
将项目源码托管到oschina中
1. 点击头像 -> 修改资料 -> SSH公钥 [如何生成SSH公钥](http://git.mydoc.io/?t=154712)
2. 创建自己的空仓储,使用 `git config --global user.name "用户名"` 和 `git config --global user.email ***@**.com` 来全局配置提交时用户的名称和邮箱
3. 使用 `git init` 在本地初始化项目
4. 使用 `touch README.md` 和 `touch .gitignore` 来创建项目的说明文件和忽略文件;
5. 使用 `git add .` 将所有文件托管到 git 中
6. 使用 `git commit -m "init project"` 将项目进行本地提交
7. 使用 `git remote add origin 仓储地址`将本地项目和远程仓储连接,并使用origin最为远程仓储的别名
8. 使用 `git push -u origin master` 将本地代码push到仓储中
App.vue 组件的基本设置
1. 头部的固定导航栏使用 `Mint-UI` 的 `Header` 组件;
2. 底部的页签使用 `mui` 的 `tabbar`;
3. 购物车的图标,使用 `icons-extra` 中的 `mui-icon-extra mui-icon-extra-cart`,同时,应该把其依赖的字体图标文件 `mui-icons-extra.ttf`,复制到 `fonts` 目录下!
4. 将底部的页签,改造成 `router-link` 来实现单页面的切换;
5. Tab Bar 路由激活时候设置高亮的两种方式:
全局设置样式如下:
.router-link-active{
color:#007aff !important;
}
或者在 `new VueRouter` 的时候,通过 `linkActiveClass` 来指定高亮的类:
// 创建路由对象
var router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' }
],
linkActiveClass: 'mui-active'
});
实现 tabbar 页签不同组件页面的切换
1. 将 tabbar 改造成 `router-link` 形式,并指定每个连接的 `to` 属性;
2. 在入口文件中导入需要展示的组件,并创建路由对象:
// 导入需要展示的组件
import Home from './components/home/home.vue'
import Member from './components/member/member.vue'
import Shopcar from './components/shopcar/shopcar.vue'
import Search from './components/search/search.vue'
// 创建路由对象
var router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/member', component: Member },
{ path: '/shopcar', component: Shopcar },
{ path: '/search', component: Search }
],
linkActiveClass: 'mui-active'
});
使用 mt-swipe 轮播图组件
1. 假数据:
lunbo: [
'http://www.itcast.cn/images/slidead/BEIJING/2017440109442800.jpg',
'http://www.itcast.cn/images/slidead/BEIJING/2017511009514700.jpg',
'http://www.itcast.cn/images/slidead/BEIJING/2017421414422600.jpg'
]
2. 引入轮播图组件:
<!-- Mint-UI 轮播图组件 -->
<div class="home-swipe">
<mt-swipe :auto="4000">
<mt-swipe-item v-for="(item, i) in lunbo" :key="i">
<img :src="item" alt="">
</mt-swipe-item>
</mt-swipe>
</div>
在`.vue`组件中使用`vue-resource`获取数据
1. 运行`cnpm i vue-resource -S`安装模块
2. 导入 vue-resource 组件
import VueResource from 'vue-resource'
3. 在vue中使用 vue-resource 组件
Vue.use(VueResource);
Promise概念:
1. Promise 是一个 构造函数,既然是构造函数, 那么,我们就可以 new Promise() 得到一个 Promise 的实例;
2. 在 Promise 上,有两个函数,分别叫做 resolve(成功之后的回调函数) 和 reject(失败之后的回调函数)
3. 在 Promise 构造函数的 Prototype 属性上,有一个 .then() 方法,也就说,只要是 Promise 构造函数创建的实例,都可以访问到 .then() 方法
4. Promise 表示一个 异步操作;每当我们 new 一个 Promise 的实例,这个实例,就表示一个具体的异步操作;
5. 既然 Promise 创建的实例,是一个异步操作,那么,这个 异步操作的结果,只能有两种状态:
5.1 状态1: 异步执行成功了,需要在内部调用 成功的回调函数 resolve 把结果返回给调用者;
5.2 状态2: 异步执行失败了,需要在内部调用 失败的回调函数 reject 把结果返回给调用者;
5.3 由于 Promise 的实例,是一个异步操作,所以,内部拿到 操作的结果后,无法使用 return 把操作的结果返回给调用者; 这时候,只能使用回调函数的形式,来把 成功 或 失败的结果,返回给调用者;
6. 我们可以在 new 出来的 Promise 实例上,调用 .then() 方法,【预先】 为 这个 Promise 异步操作,指定 成功(resolve) 和 失败(reject) 回调函数;
// 注意:这里 new 出来的 promise, 只是代表 【形式上】的一个异步操作;
// 什么是形式上的异步操作:就是说,我们只知道它是一个异步操作,但是做什么具体的异步事情,目前还不清楚
// var promise = new Promise()
// 这是一个具体的异步操作,其中,使用 function 指定一个具体的异步操作
/* var promise = new Promise(function(){
// 这个 function 内部写的就是具体的异步操作!!!
}) */
const fs = require('fs')
// 每当 new 一个 Promise 实例的时候,就会立即 执行这个 异步操作中的代码
// 也就是说,new 的时候,除了能够得到 一个 promise 实例之外,还会立即调用 我们为 Promise 构造函数传递的那个 function,执行这个 function 中的 异步操作代码;
/* var promise = new Promise(function () {
fs.readFile('./files/2.txt', 'utf-8', (err, dataStr) => {
if (err) throw err
console.log(dataStr)
})
}) */
// 初衷: 给路径,返回读取到的内容
function getFileByPath(fpath) {
var promise = new Promise(function (resolve, reject) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err) return reject(err)
resolve(dataStr)
})
})
return promise
}
var p = getFileByPath('./files/2.txt')
p.then(function (data) {
console.log(data + '-------')
}, function (err) {
console.log(err.message)
})
function getFileByPath(fpath) {
return new Promise(function (resolve, reject) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err) return reject(err)
resolve(dataStr)
})
})
}
getFileByPath('./files/2.txt')
.then(function (data) {
console.log(data + '-------')
}, function (err) {
console.log(err.message)
}) */
使用Promise 解决回调地狱:
const fs = require('fs')
function getFileByPath(fpath) {
return new Promise(function (resolve, reject) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if (err) return reject(err)
resolve(dataStr)
})
})
}
// 先读取文件1,在读取2,最后读取3
// 注意: 通过 .then 指定 回调函数的时候,成功的 回调函数,必须传,但是,失败的回调,可以省略不传
// 这是一个 错误的示范,千万不要这么用; 硬是把 法拉利,开成了 拖拉机;
/* getFileByPath('./files/1.txt')
.then(function (data) {
console.log(data)
getFileByPath('./files/2.txt')
.then(function (data) {
console.log(data)
getFileByPath('./files/3.txt')
.then(function (data) {
console.log(data)
})
})
}) */
// 读取文件1
// 在上一个 .then 中,返回一个新的 promise 实例,可以继续用下一个 .then 来处理
getFileByPath('./files/11.txt')
.then(function (data) {
console.log(data)
// 读取文件2
return getFileByPath('./files/2.txt')
})
.then(function (data) {
console.log(data)
return getFileByPath('./files/3.txt')
})
.then(function (data) {
console.log(data)
})
// 读取文件1
// 在上一个 .then 中,返回一个新的 promise 实例,可以继续用下一个 .then 来处理
// 如果 ,前面的 Promise 执行失败,我们不想让后续的Promise 操作被终止,可以为 每个 promise 指定 失败的回调
getFileByPath('./files/11.txt')
.then(function (data) {
console.log(data)
// 读取文件2
return getFileByPath('./files/2.txt')
}, function (err) {
console.log('这是失败的结果:' + err.message)
// return 一个 新的 Promise
return getFileByPath('./files/2.txt')
})
.then(function (data) {
console.log(data)
return getFileByPath('./files/3.txt')
})
.then(function (data) {
console.log(data)
})
console.log('OKOKOK')
当 我们有这样的需求: 哪怕前面的 Promise 执行失败了,但是,不要影响后续 promise 的正常执行,此时,我们可以单独为 每个 promise,通过 .then 指定一下失败的回调;
有时候,我们有这样的需求,和上面的需求刚好相反:如果 后续的Promise 执行,依赖于 前面 Promise 执行的结果,如果前面的失败了,则后面的就没有继续执行下去的意义了,此时,我们想要实现,一旦有报错,则立即终止所有 Promise的执行;
getFileByPath('./files/1.txt')
.then(function (data) {
console.log(data)
// 读取文件2
return getFileByPath('./files/22.txt')
})
.then(function (data) {
console.log(data)
return getFileByPath('./files/3.txt')
})
.then(function (data) {
console.log(data)
})
.catch(function (err) { // catch 的作用: 如果前面有任何的 Promise 执行失败,则立即终止所有 promise 的执行,并 马上进入 catch 去处理 Promise中 抛出的异常;
console.log('这是自己的处理方式:' + err.message)
})
jQuery中ajax使用promise指定成功回调函数:
$(function () {
$('#btn').on('click', function () {
$.ajax({
url: './data.json',
type: 'get',
dataType: 'json'
})
.then(function (data) {
console.log(data)
})
})
});
加载首页轮播图数据
1. 获取数据, 如何获取呢, 使用 vue-resource
2. 使用 vue-resource 的 this.$http.get 获取数据
3. 获取到的数据,要保存到 data 身上
4. 使用 v-for 循环渲染 每个 item 项
<mt-swipe :auto="4000">
<!-- 在组件中,使用v-for循环的话,一定要使用 key -->
<mt-swipe-item v-for="item in lunbotuList" :key="item.url">
<img :src="item.img" alt="">
</mt-swipe-item>
</mt-swipe>
新闻资讯 页面 制作
1. 绘制界面, 使用 MUI 中的 media-list.html
2. 使用 vue-resource 获取数据
3. 渲染真实数据
实现 新闻资讯列表 点击跳转到新闻详情
1. 把列表中的每一项改造为 router-link,同时,在跳转的时候应该提供唯一的Id标识符
2. 创建新闻详情的组件页面 NewsInfo.vue
3. 在 路由模块中,将 新闻详情的 路由地址 和 组件页面对应起来
单独封装一个 comment.vue 评论子组件
1. 先创建一个 单独的 comment.vue 组件模板
2. 在需要使用 comment 组件的 页面中,先手动 导入 comment 组件
`import comment from './comment.vue'`
3. 在父组件中,使用 `components` 属性,将刚才导入 comment 组件,注册为自己的 子组件
4. 将注册子组件时候的,注册名称,以 标签形式,在页面中 引用即可
发表评论
1. 把文本框做双向数据绑定
2. 为发表按钮绑定一个事件
3. 校验评论内容是否为空,如果为空,则Toast提示用户 评论内容不能为空
4. 通过 vue-resource 发送一个请求,把评论内容提交给 服务器
5. 当发表评论OK后,重新刷新列表,以查看最新的评论
+ 如果调用 getComments 方法重新刷新评论列表的话,可能只能得到 最后一页的评论,前几页的评论获取不到
+ 换一种思路: 当评论成功后,在客户端,手动拼接出一个 最新的评论对象,然后 调用 数组的 unshift 方法, 把最新的评论,追加到 data 中 comments 的开头;这样,就能 完美实现刷新评论列表的需求;
制作顶部滑动条的坑: 使用mui的`tab-top-webview-main`完成分类滑动栏
1. 需要借助于 MUI 中的 tab-top-webview-main.html
2. 需要把 slider 区域的 mui-fullscreen 类去掉
3. 滑动条无法正常触发滑动,通过检查官方文档,发现这是JS组件,需要被初始化一下:
+ 导入 mui.js
+ 调用官方提供的 方式 去初始化:
mui('.mui-scroll-wrapper').scroll({
deceleration: 0.0005 //flick 减速系数,系数越大,滚动速度越慢,滚动距离越小,默认值0.0006
});
4. 我们在初始化 滑动条 的时候,导入的 mui.js ,但是,控制台报错: `Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode`
+ 经过我们合理的推测,觉得,可能是 mui.js 中用到了 'caller', 'callee', and 'arguments' 东西,但是, webpack 打包好的 bundle.js 中,默认是启用严格模式的,所以,这两者冲突了;
+ 解决方案: 1. 把 mui.js 中的 非严格 模式的代码改掉;但是不现实; 2. 把 webpack 打包时候的严格模式禁用掉;
+ 最终,我们选择了 plan B 移除严格模式: 使用这个插件 babel-plugin-transform-remove-strict-mode
5. 刚进入 图片分享页面的时候, 滑动条无法正常工作, 经过我们认真的分析,发现, 如果要初始化 滑动条,必须要等 DOM 元素加载完毕,所以,我们把 初始化 滑动条 的代码,搬到了 mounted 生命周期函数中;
6. 当 滑动条 调试OK后,发现, tabbar 无法正常工作了,这时候,我们需要把 每个 tabbar 按钮的 样式中 `mui-tab-item` 重新改一下名字;
7. 获取所有分类,并渲染 分类列表;
兼容问题
1. 和 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;
}
2. `tab-top-webview-main`组件第一次显示到页面中的时候,无法被滑动的解决方案:
先导入 mui 的JS文件:
import mui from '../../../lib/mui/js/mui.min.js'
在 组件的 `mounted` 事件钩子中,注册 mui 的滚动事件:
mounted() {
// 需要在组件的 mounted 事件钩子中,注册 mui 的 scroll 滚动事件
mui('.mui-scroll-wrapper').scroll({
deceleration: 0.0005 //flick 减速系数,系数越大,滚动速度越慢,滚动距离越小,默认值0.0006
});
}
3. 滑动的时候报警告:`Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080`
解决方法,可以加上* { touch-action: pan-y; } 这句样式去掉。
原因:(是chrome为了提高页面的滑动流畅度而新折腾出来的一个东西) http://www.cnblogs.com/pearl07/p/6589114.html
https://developer.mozilla.org/zh-CN/docs/Web/CSS/touch-action
移除严格模式
[babel-plugin-transform-remove-strict-mode](https://github.com/genify/babel-plugin-transform-remove-strict-mode)
[vue-preview](https://github.com/LS1231/vue-preview)
一个Vue集成PhotoSwipe图片预览插件
实现 图片详情中 缩略图的功能
1. 使用 插件 vue-preview 这个缩略图插件
2. 获取到所有的图片列表,然后使用 v-for 指令渲染数据
3. 注意: img标签上的class不能去掉
4. 注意: 每个 图片数据对象中,必须有 w 和 h 属性
尝试在手机上 去进行项目的预览和测试
1. 要保证 手机 和 开发项目的电脑 处于同一个 WIFI 环境中,也就是说 手机 可以 访问到 电脑的 IP
2. 打开自己的 项目中 package.json 文件,在 dev 脚本中,添加一个 --host 指令, 把 当前 电脑的 WIFI IP地址, 设置为 --host 的指令值;
+ 如何查看自己电脑所处 WIFI 的IP呢, 在 cmd 终端中运行 `ipconfig` , 查看 无线网的 ip 地址
Total Control 软件 与手机的共屏软件(Android可用)
<!-- 在网页中,有两种跳转方式: -->
<!-- 方式1: 使用 a 标签 的形式叫做 标签跳转 -->
<!-- 方式2: 使用 window.location.href 的形式,叫做 编程式导航 -->
<div class="goods-item" v-for="item in goodslist" :key="item.id" @click="goDetail(item.id)">
goDetail(id) {
// 使用JS的形式进行路由导航
// 注意: 一定要区分 this.$route 和 this.$router 这两个对象,
// 其中: this.$route 是路由【参数对象】,所有路由中的参数, params, query 都属于它
// 其中: this.$router 是一个路由【导航对象】,用它 可以方便的 使用 JS 代码,实现路由的 前进、后退、 跳转到新的 URL 地址
console.log(this);
id: this.$route.params.id, // 将路由参数对象中的 id 挂载到 data , 方便后期调用
// 1. 最简单的
// this.$router.push("/home/goodsinfo/" + id);
// 2. 传递对象
// this.$router.push({ path: "/home/goodsinfo/" + id });
// 3. 传递命名的路由
this.$router.push({ name: "goodsinfo", params: { id } });
}
小球动画优化思路:
1. 导致 动画 不准确的 本质原因: 我们把 小球 最终 位移到的 位置 横纵坐标 直接写死了, 应该 根据不同情况,动态计算这个坐标值;
2.思路: 先得到 徽标的 横纵 坐标,再得到 小球的 横纵坐标,然后 让 y 值 求差, x 值也求 差,得到 的结果,就是横纵坐标要位移的距离
3. 如何 获取 徽标和小球的 位置??? domObject.getBoundingClientRect()
beforeEnter(el) {
el.style.transform = "translate(0, 0)";
},
enter(el, done) {
el.offsetWidth;
// 获取小球的 在页面中的位置
const ballPosition = this.$refs.ball.getBoundingClientRect();
// 获取 徽标 在页面中的位置
const badgePosition = document .getElementById("badge") .getBoundingClientRect();
const xDist = badgePosition.left - ballPosition.left;
const yDist = badgePosition.top - ballPosition.top;
el.style.transform = `translate(${xDist}px, ${yDist}px)`;
el.style.transition = "all 0.5s cubic-bezier(.4,-0.3,1,.68)";
done();
},
afterEnter(el) {
this.ballFlag = !this.ballFlag;
},
git使用推送:
git add .
git commit -m "打卡了"
git push
git status
vuex:
概念: vuex 是 Vue 配套的 公共数据管理工具,它可以把一些共享的数据,保存到 vuex 中,方便 整个程序中的任何组件直接获取或修改我们的公共数据;
// 配置vuex的步骤
// 1. 运行 cnpm i vuex -S
// 2. 导入包
import Vuex from 'vuex'
// 3. 注册vuex到vue中
Vue.use(Vuex)
// 4. new Vuex.Store() 实例,得到一个 数据仓储对象
var store = new Vuex.Store({
state: {
// 大家可以把 state 想象成 组件中的 data ,专门用来存储数据的
// 如果在 组件中,想要访问,store 中的数据,只能通过 this.$store.state.*** 来访问
count: 0
},
mutations: {
// 注意: 如果要操作 store 中的 state 值,只能通过 调用 mutations 提供的方法,才能操作对应的数据,不推荐直接操作 state 中的数据,因为 万一导致了数据的紊乱,不能快速定位到错误的原因,因为,每个组件都可能有操作数据的方法;
increment(state) {
state.count++
},
subtract(state,obj){
// 注意: mutations 的 函数参数列表中,最多支持两个参数,其中,参数1: 是 state 状态; 参数2: 通过 commit 提交过来的参数;
console.log(obj)
state.count -= (obj.c + obj.d)
}
},
// 注意: 如果组件想要调用 mutations 中的方法,只能使用 this.$store.commit('方法名')
//this.$store.commit("increment") this.$store.commit("subtract", { c: 3, d: 1 });
// 这种 调用 mutations 方法的格式,和 this.$emit('父组件中方法名')
getters: {
// 注意:这里的 getters, 只负责 对外提供数据,不负责 修改数据,如果想要修改 state 中的数据,请 去找 mutations
optCount: function (state) {
return '当前最新的count值是:' + state.count
}
}
})
// 总结:
// 1. state中的数据,不能直接修改,如果想要修改,必须通过 mutations
// 2. 如果组件想要直接 从 state 上获取数据: 需要 this.$store.state.***
// 3. 如果 组件,想要修改数据,必须使用 mutations 提供的方法,需要通过 this.$store.commit('方法的名称', 唯一的一个参数)
// 4. 如果 store 中 state 上的数据, 在对外提供的时候,需要做一层包装,那么 ,推荐使用 getters, 如果需要使用 getters ,则用 this.$store.getters.***
开启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,值越大表示压缩的越厉害。
使用ngrok将本机映射为一个外网的Web服务器
注意:由于默认使用的美国的服务器进行中间转接,所以访问速度炒鸡慢,访问时可启用FQ软件,提高网页打开速度!
基本命令:
1. 双击快速运行 ngrok.exe 应用程序
2. 随机生成三级域名(自动防止重名问题),其中,80代表要向外暴露的端口号:
ngrok http [80]
或者人为指定三级域名(存在重名问题):
ngrok http -subdomain=[inconshreveable] [80]