功能技术分析
- 前端 vue - router
- 后端 vue - resource
- 第三方javascript库better-scroll 最大程度组件化
动手敲每一行代码
学习目标
- 掌握Vue.js 在实战中的应用
- 学会使用Vue.js 完整开发移动端App
- 学会组件化,模块化开发方式
学习内容
- Vue.js 框架介绍
- Vue-cli 脚手架 搭建基本代码框架
- vue-router 官方插件管理路由
- vue-resource Ajax通信
- Webpack 构建工具
- es6 + eslint eslint:es6 代码风格检查工具
- 工程化
- 组件化
- 模块化
- 移动端常用开发技巧
近年来前端开发趋势
- 旧浏览器逐渐淘汰,移动端需求增加
- 前端交互越来越多
- 架构从传统后代MVC 想REST API + 前端 MV* 迁移
MVVM框架
- 针对具有复杂交互逻辑的前端应用
- 提供基础的架构抽象
- 通过Ajax数据持久化,保证前端体验
什么是vue.js
- 它是一个轻量级mvvm框架
- 数据驱动+ 组件化前端开发
- github star数量 社区完善
对比Angular React
- Vue.js 更轻量,gzip 后大小只有20k
- Vue.js 更容易上手,学习曲线平稳
- 吸取两个之长,借鉴了angular 的指令和react的组件化
Vue.js核心思想
- 数据驱动
- 组件化
数据驱动
DOM是数据的一种自然映射
数据响应原理
数据(model) 改变驱动视图(view) 自然更新
组件化
扩展HTML元素 ,封装可重用的代码
组件设计原则
- 页面上每个独立的可视。可交互区域视为一个组件
- 每个组件对应一个工程目录,组件所需要的各种资源在这个目录下就近维护
- 页面不过是组件的容器,组件可以嵌套自由组合形成完整的页面
3-Vue-cli 介绍
Vue-cli是Vue的脚手架工具 帮助我们搭建基础代码
- 目录结构
- 本地调试
- 代码部署
- 热加载
- 单元测试
学习一个开源项目最好的方法:去github 看readme
https://github.com/vuejs/vue-cli
npm install -g @vue/cli-init
# vue init now works exactly the same as vue-cli@2.x
vue init webpack my-project
项目文件目录
- build webpack配置文件相关文件
- config wenback配置相关文件
- node_modules npm install 安装的依赖代码库
- src 存放项目源码
- common
- js
- fonts
- stylus
- components
- header
- common
- .babelrc babel 相关配置
- .editorconfig 编辑器配置
- .eslintignore 忽略语法检查的目录文件
- .eslintrc.js eslint的配置文件
- .gitignore git 忽略检查文件
- package.json 项目配置文件
- README.md 项目说明
4-5mock数据(模拟后台数据)
在github看项目API,真是爽的不要不要的。
语法检查,让你写的代码更加规范
border-1px处理方式
- mixin.styl文件
border-1px($color)
position: relative
&:after
display: block
position: absolute
left: 0
bottom: 0
width: 100%
border-top: 1px solid $color
content: ' '
- 引入
@import "./common/stylus/mixin.styl"
- 使用css属性
border-1px(rgba(7, 17, 27, 0.1))
- DOM 添加class
border-1px
- .border-1px媒体查询像素适配
@media (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5)
.border-1px
&::after
-webkit-transform: scaleY(0.7)
transform: scaleY(0.7)
@media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2)
.border-1px
&::after
-webkit-transform: scaleY(0.5)
transform: scaleY(0.5)
css文件引入其他css文件方式
@import "./mixin"
@import "./icon"
@import "./base"
引入公共css文件方式
import 'common/stylus/index.styl'
axios ajax 前后端数据通信
统一设置数据请求返回的状态码
const ERR_OK = 0
对于img 一般固定宽高防止浏览器重绘
<img width="64" height='64'>
去除子元素空白字符间隙
父元素设置 font-size: 0
行内元素设置宽度高度
需要设置 display:inline-block
统一处理背景图@2x @3x 图片
// 第一步
bg-image($url)
background-image: url($url + "@2x.png")
@media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3)
background-image: url($url + "@3x.png")
// 第二步
@import "~common/stylus/mixin"
// 第三部步
bg-image('logo')
设置行内块元素对齐方式
vertical-align:***
我和很喜欢这样设置初始化html body
html, body {
line-height: 1; //这里高度是和当前设置字体大小一样的高度
font-weight: 200;
font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial, sans-serif, 'Droid Sans Fallback'
}
数据抽象 不同数据对应不同class从而实现不同icon
.support
.icon
display: inline-block
vertical-align: top
width: 12px
height: 12px
margin-right: 4px
background-size: 12px 12px
background-repeat: no-repeat
&.decrease
bg-image('decrease_1')
&.discount
bg-image('discount_1')
&.guarantee
bg-image('guarantee_1')
&.invoice
bg-image('invoice_1')
&.special
bg-image('special_1')
<span class="icon" :class="classMap[seller.supports[0].type]"></span>
this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'];
chrome 浏览器字体最小为12px
blur属性
filter: blur(10px)
clearfix
display: inline-block
&:after
display:block
content:''
height: 0
line-height: 0
clear: both
visibility: hidden
css sticky footer
//html
<div ckass="detail-wrapper clearfix">
<div class="detail-main"></div>
</div>
<div class="detail-close"></div>
//
.detail-wrapper
width: 100%
min-height: 100%
.detail-main
padding-bottom: 64px
.detail-close
position: relative
width: 32px
height: 32px
margin: -64px auto 0 auto
clear: both
font-size: 32px
star组件
<div class="star" :class="starType">
<span v-for="itemClass in itemClasses" :class="itemClass" class="star-item" track-by="$index"></span>
</div>
@import "../../common/stylus/mixin.styl"
.star
font-size: 0
.star-item
display: inline-block
background-repeat: no-repeat
&.star-48
.star-item
width: 20px
height: 20px
margin-right: 22px
background-size: 20px 20px
&:last-child
margin-right: 0
&.on
bg-image('star48_on')
&.half
bg-image('star48_half')
&.off
bg-image('star48_off')
&.star-36
.star-item
width: 15px
height: 15px
margin-right: 6px
background-size: 15px 15px
&:last-child
margin-right: 0
&.on
bg-image('star36_on')
&.half
bg-image('star36_half')
&.off
bg-image('star36_off')
&.star-24
.star-item
width: 10px
height: 10px
margin-right: 3px
background-size: 10px 10px
&:last-child
margin-right: 0
&.on
bg-image('star24_on')
&.half
bg-image('star24_half')
&.off
bg-image('star24_off')
const LENGTH = 5;
const CLS_ON = 'on';
const CLS_HALF = 'half';
const CLS_OFF = 'off';
export default {
props: {
size: {
type: Number
},
score: {
type: Number
}
},
computed: {
starType() {
return 'star-' + this.size;
},
itemClasses() {
let result = [];
let score = Math.floor(this.score * 2) / 2;
let hasDecimal = score % 1 !== 0;
let integer = Math.floor(score);
for (let i = 0; i < integer; i++) {
result.push(CLS_ON);
}
if (hasDecimal) {
result.push(CLS_HALF);
}
while (result.length < LENGTH) {
result.push(CLS_OFF);
}
return result;
}
}
};
<star :size="48" :score="seller.score"></star>
transition
<!-- 简单元素 -->
<transition>
<div v-if="ok">toggled content</div>
</transition name="fade">
.detail
position: fixed
z-index: 100
top: 0
left: 0
width: 100%
height: 100%
overflow: auto
transition: all 0.5s
backdrop-filter: blur(10px)
&.fade-transition
opacity: 1
background: rgba(7, 17, 27, 0.8)
&.fade-enter, &.fade-leave
opacity: 0
background: rgba(7, 17, 27, 0)
flex
flex: 0 0 80px 第一个参数等分 第二个参数空间不足缩放情况 第三个参数站位空间
width: 80px
computed
computed: {
currentIndex() {
for (let i = 0; i < this.listHeight.length; i++) {
let height1 = this.listHeight[i];
let height2 = this.listHeight[i + 1];
if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {
return i;
}
}
return 0;
}
}
$refs
this.$refs.foodsWrapper.getElementsByClassName('food-list-hook')
box-sizing
border-box
content-box
box-shadow
box-shodow: 0 4px 8px 0 rgba(0,0,0,.4)
ES6
`¥${this.minPrice}元起送`
新增或者删除某个字段的时候
Vue.set(this.food, 'count', 1)
vue派发事件
this.$emit('give-advice', this.possibleAdvice[randomAdviceIndex])
访问组件
<child-component ref="child"></child-component>
在vue中data数据里面的名称,不要和方法名称相同
神奇css黑魔法 自适应宽高百分比
position: realtive
width: 100%
height: 0
padding-top: 100%
fromDate
export function formatDate(date, fmt) {
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
let o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
};
for (let k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
let str = o[k] + '';
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
}
}
return fmt;
};
function padLeftZero(str) {
return ('00' + str).substr(str.length);
}
解析url参数
export function urlParse() {
let url = window.location.search;
let obj = {};
let reg = /[?&][^?&]+=[^?&]+/g;
let arr = url.match(reg);
// ['?id=12345', '&a=b']
if (arr) {
arr.forEach((item) => {
let tempArr = item.substring(1).split('=');
let key = decodeURIComponent(tempArr[0]);
let val = decodeURIComponent(tempArr[1]);
obj[key] = val;
});
}
return obj;
};
存储本地
export function saveToLocal(id, key, value) {
let seller = window.localStorage.__seller__;
if (!seller) {
seller = {};
seller[id] = {};
} else {
seller = JSON.parse(seller);
if (!seller[id]) {
seller[id] = {};
}
}
seller[id][key] = value;
window.localStorage.__seller__ = JSON.stringify(seller);
};
export function loadFromLocal(id, key, def) {
let seller = window.localStorage.__seller__;
if (!seller) {
return def;
}
seller = JSON.parse(seller)[id];
if (!seller) {
return def;
}
let ret = seller[key];
return ret || def;
};
组件状态保持
keep-alive
打包vue 项目开发的项目
npm run build