$ npm install -g @vue/cli (安装vue-cli)
$ vue create .(创建项目在根目录下)
$ vue create +项目名 (创建项目在自定义项目名目录下)
rem配置(全局引入的文件在main.js中引入)
网易方案
function font () {
document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75 + 'px'
}
font() // 表示页面一开启就转换单位
window.onresize = font // 表示页面尺寸发生改变时,再次计算rem
淘宝方案
(function(designWidth, maxWidth) {
var doc = document,
win = window,
docEl = doc.documentElement,
remStyle = document.createElement("style"),
tid;
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
maxWidth = maxWidth || 540;
width>maxWidth && (width=maxWidth);
var rem = width * 100 / designWidth;
remStyle.innerHTML = 'html{font-size:' + rem + 'px;}';
}
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(remStyle);
} else {
var wrap = doc.createElement("div");
wrap.appendChild(remStyle);
doc.write(wrap.innerHTML);
wrap = null;
}
//要等 wiewport 设置好后才能执行 refreshRem,不然 refreshRem 会执行2次;
refreshRem();
win.addEventListener("resize", function() {
clearTimeout(tid); //防止执行两次
tid = setTimeout(refreshRem, 300);
}, false);
win.addEventListener("pageshow", function(e) {
if (e.persisted) { // 浏览器后退的时候重新计算
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
if (doc.readyState === "complete") {
doc.body.style.fontSize = "16px";
} else {
doc.addEventListener("DOMContentLoaded", function() {
doc.body.style.fontSize = "16px";
}, false);
}
})(375, 750);
// 备注: 这里的375本身就应该写成750 ,但是写成750之后,我们设计稿的尺寸要/50,不好算,我就想,除以100更好算,所以我改成了375
阿里方案
/*
通过js来动态添加rem
*/
(function(designWidth, maxWidth) {
var doc = document,
win = window,
docEl = doc.documentElement,
remStyle = document.createElement("style"),
tid;
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
maxWidth = maxWidth || 540;
width>maxWidth && (width=maxWidth);
var rem = width * 100 / designWidth;
remStyle.innerHTML = 'html{font-size:' + rem + 'px;}';
}
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(remStyle);
} else {
var wrap = doc.createElement("div");
wrap.appendChild(remStyle);
doc.write(wrap.innerHTML);
wrap = null;
}
//要等 wiewport 设置好后才能执行 refreshRem,不然 refreshRem 会执行2次;
refreshRem();
win.addEventListener("resize", function() {
clearTimeout(tid); //防止执行两次
tid = setTimeout(refreshRem, 300);
}, false);
win.addEventListener("pageshow", function(e) {
if (e.persisted) { // 浏览器后退的时候重新计算
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
if (doc.readyState === "complete") {
doc.body.style.fontSize = "16px";
} else {
doc.addEventListener("DOMContentLoaded", function() {
doc.body.style.fontSize = "16px";
}, false);
}
})(375, 750);
// 备注: 这里的375本身就应该写成750 ,但是写成750之后,我们设计稿的尺寸要/50,不好算,我就想,除以100更好算,所以我改成了375
src/layout/index.vue 项目的布局外壳
src/components 项目的公共组件在layout中使用
src/utils 项目的公共封装库
src/pages 项目的路由路径对应的组件
src/store 状态管理vuex的配置文件夹
src/router 创建路由配置文件夹
mock.service 数据模拟
const path = require('path')
module.exports = {
chainWebpack: config => {
config.resolve.alias
.set('@', path.join( __dirname, './src' ))
.set('assets', path.join( __dirname, './src/assets' ))
.set('components', path.join( __dirname, './src/components' ))
},
//反向代理(此处用了猫眼,亲亲网数据)
devServer: {
open: true,
proxy: {
'/ajax': {
target: 'http://m.maoyan.com',
changeOrigin: true
},
'/index.php': {
target: 'http://www.qinqin.net',
changeOrigin: true
}
}
}
}
百度去搜
记得安装axios插件 $ yarn add axios
/*
* 这个东西到底我们封装成什么?
* 类
* 对象
* 函数 √
*/
// const baseURL = 'http://localhost:3000' // 本地启动
// const baseURL = 'http://10.31.154.189:3000' // 公司局域网启动
// const baseURL = 'http://10.31.154.110:3000' // 测试环境
// const baseURL = 'http://10.31.154.456:3000' // 上线环境
import axios from 'axios'
import { Toast } from 'vant'
import Vue from 'vue'
Vue.use( Toast )
const instance = axios.create({ // 创建一个axios实例
// baseURL,
timeout: 5000,
});
// 默认表单提交
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
// 如果你没有登录,项目中任何页面你都进不去-后台管理系统
// 自定义加载图标
Toast.loading({
message: '加载中...',
forbidClick: true,
loadingType: 'spinner'
});
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
Toast.clear()
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
const request = ({
url,
method = "GET",
params,
data,
withCredentials = false, // default
headers
}) => {
return new Promise(( resolved,rejected ) => {
// 区别两个不同的数据请求就行 get post
switch ( method.toUpperCase() ) {
case 'POST':
var real_data = {}
if ( headers['Content-Type'] == 'application/x-www-form-urlencoded' ){
// 表单提交
const p = new URLSearchParams()
for ( let key in data ) {
p.append( key, data[ key ] )
}
real_data = p
} else {
// json提交
real_data = data
}
instance.post( url,data = real_data, {
withCredentials, // default
headers
}).then( res => resolved( res ))
.catch( err => rejected( err ))
break;
case 'PUT':
instance.put( url, {
method,
params,
withCredentials, // default
headers
}).then( res => resolved( res ))
.catch( err => rejected( err ))
break;
default:
instance.get( url, {
method,
params,
withCredentials, // default
headers
}).then( res => resolved( res ))
.catch( err => rejected( err ))
break;
}
})
}
export default request
// module.exports = request
在public/index.html中修改,另外还可以在改文件下使用font-awesome,使用组件
引入border.styl文件
border($border-width = 1px, $border-color = #ccc, $border-style = solid, $radius = 0)
// 为边框位置提供定位参考
position: relative;
if $border-width == null
$border-width: 0;
border-radius: $radius;
&::after
// 用以解决边框layer遮盖内容
pointer-events: none;
position: absolute;
z-index: 999;
top: 0;
left: 0;
// fix当元素宽度出现小数时,边框可能显示不全的问题
// overflow: hidden;
content: "\0020";
border-color: $border-color;
border-style: $border-style;
border-width: $border-width;
// 适配dpr进行缩放
@media (max--moz-device-pixel-ratio: 1.49), (-webkit-max-device-pixel-ratio: 1.49), (max-device-pixel-ratio: 1.49), (max-resolution: 143dpi), (max-resolution: 1.49dppx)
width: 100%;
height: 100%;
if $radius != null {
border-radius: $radius;
}
@media (min--moz-device-pixel-ratio: 1.5) and (max--moz-device-pixel-ratio: 2.49), (-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 2.49),(min-device-pixel-ratio: 1.5) and (max-device-pixel-ratio: 2.49),(min-resolution: 144dpi) and (max-resolution: 239dpi),(min-resolution: 1.5dppx) and (max-resolution: 2.49dppx)
width: 200%;
height: 200%;
transform: scale(.5)
if $radius != null {
border-radius: $radius * 2;
}
@media (min--moz-device-pixel-ratio: 2.5), (-webkit-min-device-pixel-ratio: 2.5),(min-device-pixel-ratio: 2.5), (min-resolution: 240dpi),(min-resolution: 2.5dppx)
width: 300%;
height: 300%;
transform: scale(.33333)
if $radius != null {
border-radius: $radius * 3;
}
transform-origin: 0 0;
$ npm i vue-router -S
// 1. 引入所需要模块
import Vue from 'vue'
import VueRouter from 'vue-router'
import routerTable from './routes'
// 2. 安装插件
Vue.use( VueRouter )
// // 3. 创建路由表(单独创建一个文件routes.js)
// const routerTable = []
// 4. 创建路由模块
// const router = new VueRouter( options )
const router = new VueRouter({
mode: 'history',// 需要后端配置
routes: routerTable // 配置路由表
})
export default router
// 引入路由模块
import router from '@/router'
new Vue({
// router: router,
router,// 为全局注入路由,我们会得到两个属性 $router $route
store,
render: h => h(App),
}).$mount('#app')
{
path: '/home',
component: Home, // 路由路径对应渲染的组件
children: [ // 子路由表
// 一个对象就是一个子路由配置
{
path: 'hot',// 子路由不加 /
component: HotMovieComp,
name: 'hot', // 给路由起了个名字
meta: { // 元信息
include: 'HotMovieComp'
}
}
]
}
<!-- to是路由路径 tag渲染是什么就渲染什么标签 -->
<!--写法一-->
<!-- <router-link to = "/home/hot" tag = "li" active-class = "active"> 正在热映 </!-->
<!--写法二 就是方便一些,注意to前加: -->
<router-link :to = "{ name: 'hot' }" tag = "li" active-class = "active"> 正在热映 </router-link>
<router-link :to = "{ name: 'coming' }" tag = "li" active-class = "active"> 即将上映 </router-link>
这是定义的元信息,keep-alive 上有个include,可以隐藏执行后台的选项,就只显示被切换到的组件
meta: { // 元信息
include: 'HotMovieComp'
}
使用vue提供的内置组件 router-view 来接收路由对一个的组件,外层套实现缓存,放后台缓存,缓存速度快
<keep-alive
:include="$route.meta.include"
>
<router-view></router-view>
</keep-alive>
$ npm i mint-ui -D/npm i vant -D
$ npm install babel-plugin-component -D/npm install babel-plugin-import -D
通过在path后加:id
1.动态路由中的路由传参 - 路由接参
- $route
2.编程式导航
- $router
- push vs replace
- push会将我们操作放入历史记录,点击浏览器返回会一层一层返回
- replace不会将我们的操作放入历史记录,点击浏览器返回反回2层
- 在filters中定义方法
import Vue from 'vue'
Vue.filter('curreny',val => {
return '¥ ' + val
})
- 在main.js中导入import ‘@/filters’
3. 在需要使用的地方加‘|curreny’(|是管道符)
对路由做判断,可以用watch进行监听
watch: {
$route: {
deep: true,
handler () {
if ( this.$route.name == 'detail') {
this.tabBarFlag = false
} else {
this.tabBarFlag = true
}
}
}
}
1.封装一个storage.js
export const getStorage = ( name ) => {
return localStorage.getItem( name ) && JSON.parse(localStorage.getItem( name )) || []
}
export const saveStorage = ( name, value ) => {
localStorage.setItem( name,JSON.stringify(value) )
}
2.在加购页面时,使用storage.js
addShopCar () {
// 1. 获取数据 getStorage
const data = getStorage('shopcar') // [] || [{}]
if ( data.length != 0 ) {
// 有数据 判断是不是同一个商品
const f = data.some( item => item.id == this.$route.params.id )
if ( f ) {
// true 同一个商品
data.map( item => {
if ( item .id == this.$route.params.id ) {
item.num += this.num
return
}
})
} else {
// false 不同商品
data.push({
...this.$route.query,
num: this.num
})
}
} else {
// 没有数据
// data.push(当前商品信息)
data.push({
...this.$route.query,
num: this.num
})
}
saveStorage('shopcar', data )
}
作用:对路由的跳转进行拦截
类型:
- 1.全局导航守卫:对整个项目做控制
- 2.路由独享守卫:对单个路由的路由配置做控制,写在路由表中
- 3.组件内守卫:对组件对应的路由做控制
使用:
- 1.全局导航守卫
- 全局前置守卫( router.beforeEach(to,from,next))
- 全局后置守卫( router.afterEach(to,from,next))
- 全局更新守卫【 可忽略, 使用和前置守卫一样 】
- 区别: 是否完整遍历路由表
- 更新守卫要遍历完整路由表之后才跳转页面
使用场景:
- 全局前置守卫:后台管理系统||社区app
router.beforeEach(( to,from,next ) => { console.log("lucia: to", to) console.log("lucia: from", from) // console.log("lucia: next", next) // next( false ) // next() == next( true ) // next( true ) if ( to.path == '/home/hot' ) { next() } const token = getCookie(' _token') console.log('token',token) if ( token || to.path == '/login') { next() } else { next('/login') }
使用方法:
1.导航守卫参数 /home -> /category
1. from 表示当前路由 /home
2. to 表示目标路由 /category
3. next [必须要写的]
3.1. next表示两者之间的连接
3.2. 取值
3.2.1. next() //默认连通
3.2.2. next( true ) // 表示连通
3.2.3. next( false ) // 不表示断开连接
3.2.4. next(路由路径)
3.2.4.1. next(’/home’)
3.2.4.2. next({ name: ‘home’, params: {},query: {} })
3.2.5. next( vm => { } ) // vm指的是目标组件
- 全局后置守卫:没有拦截这一功能,只有提示功能,参数只有to,from,功能同上
- 2.路由独享守卫
使用方法:在router/router.js中需要守卫的组件{ path: '/shopcar', component: ShopCar, beforeEnter ( to,from,next ) { console.log('to',to) const token = getCookie('_token') if ( token ) { console.log('token') next() } else { Toast.fail('您还没有登录,无法查看购物车,请先去登录') next( false ) } }, meta: { include: 'ShopCar' } }
- 3.组件内守卫
使用方法:
- 组件前置守卫【 钩子 】(beforeRouterEnter)
- 拦截组件的进入
- 执行: 创建创建前执行,这个时候没有组件,没有this
- 功能
- 判断是否有token,然后是否能进入当前页面
- 数据预载
- 进入组件前,提前得到数据,并将这个数据赋值给组件
- 组件后置守卫【 钩子 】(afterRouterEnter)
- 拦截组件离开
- 执行: 离开组件前,这个时候有组件,有this
- 组件更新守卫
- 专用于: 动态路由
export const getCookie = name => {
var cookies = {};
if(document.cookie){
var objs = document.cookie.split(';');
for(var i in objs){
var index = objs[i].indexOf('=');
var key = objs[i].substr(0,index);
var value = objs[i].substr(index+1,objs[i].length);
cookies[key] = value;
}
}
return cookies[ name ];
}
export const setCookie = ( name, value, opts ) => {
//opts: maxAge,path,domain,secure
if(name && value){
var cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value);
if(opts){
if(opts.maxAge){
cookie += ';max-age=' + opts.maxAge;
}
if(opts.path){
cookie += ';max-age=' + opts.path;
}
if(opts.domain){
cookie += ';max-age=' + opts.doamin;
}
if(opts.secure){
cookie += ';max-age=' + opts.secure;
}
}
document.cookie = cookie;
return cookie;
}else{
return '';
}
}
export const remove = key => {
if(getCookie()[key]){
document.cookie = key + '=;max-age=0';
}
}
export const clear = () => {
var cookies = getCookie();
for(var i in cookies){
document.cookie = cookies[i] + '=;max-age=0';
}
}
安装: yarn add animate.css /npm i animate.css -S
现在main.js中导入animate.js
import 'animate.css'
在动画组件/元素外层加一个 Vue 内置组件 transition
在transition标签内写enter-active-class=“animated 进入动画类名” leave-active-class=“animated 离开动画类名”,还要加个mode属性:in-out(先进后出)/out-in(先出后进)
1.什么是Vuex?
2.Vuex的好处
3.Vuex在什么场景下应该使用
4.Vuex使用流程
5.Vuex的核心组成部分:
actions和mutations共同承担一部分vm的逻辑
6.安装 yarn add vuex /npm i vuex -S
7.简易版vuex
import store from '@/store'
new Vue({
// router: router,
router,// 为全局注入路由,我们会得到两个属性 $router $route
store,// 为全局注入store,我们会得到一个属性 $store
render: h => h(App),
}).$mount('#app')
// 1. 引入vuex
import Vuex from 'vuex'
import Vue from 'vue'
// 引入动作常量
import { INCREMENT,DECRMENT } from './actionType'
Vue.use( Vuex )
// 2. 创建store.js
// const store = new Vuex.Store( options )
const store = new Vuex.Store({
state: {
count: 0
},
actions: {
// 对象中存放的是创建动作的方法
increment ( store,payload ) {
// store就是store实例
// payload 负载 就是组件视图传递给我们的数据
// console.log(payload)
// console.log('increment')
// 创建动作
const action = {
type: INCREMENT,// type是一个类型,表示组件中完成了什么类型的动作
payload
}
// 在由store发送动作
store.commit( action ) // 将action动作发给了mutations
},
decrement ({ commit }) {
commit({
type: DECRMENT
})
}
},
mutations: {
// 对象中存放的是修改 state 的方法
[ INCREMENT ] ( state ,action) {
// 第一个参数就是 state
// 第二个参数就是actions中发过来的action
// 修改数据
state.count += action.payload
state.count ++
},
[ DECRMENT ] ( state ) {
state.count --
}
},
// getters
})
export default store
8.项目中如何使用vuex(利用辅助工具)
import store from '@/store'
new Vue({
// router: router,
router,// 为全局注入路由,我们会得到两个属性 $router $route
store,// 为全局注入store,我们会得到一个属性 $store
render: h => h(App),
}).$mount('#app')
/*
* home的vuex
* 很多公司
* actions直接不写
*/
import request from '@/utils/request'
import api from '@/api'
// 常量定义
const GET_HOT_MOVIES = 'GET_HOT_MOVIES'
const GET_NEW_MOVIES = 'GET_NEW_MOVIES'
export default {
state: {
movies: {}
},
actions: {
async getMovies ({ commit }) {
// 发送数据请求
const result = await request({
url: api.home.getHotMovie,
params: {
token: '',
optimus_uuid: 'F2C8CA50060111E9BE3B2FA6BBC8F42F783446B54C974221B87A48F0BB404BD5',
optimus_risk_level: 71,
optimus_code: 10
}
})
commit({
type: GET_HOT_MOVIES,
payload: result.data
})
},
async getNewMovies ({ commit },id) {
const result = await request({
url: api.home.getComingMovie,
params: {
token: '',
movieIds: id,
optimus_uuid: 'F2C8CA50060111E9BE3B2FA6BBC8F42F783446B54C974221B87A48F0BB404BD5',
optimus_risk_level: 71,
optimus_code: 10
}
})
commit({
type: GET_NEW_MOVIES,
payload: result.data.coming
})
}
},
mutations: {
[ GET_HOT_MOVIES ] ( state,action ) {
// 修改数据
state.movies = action.payload
},
[ GET_NEW_MOVIES ] ( state,action ) {
state.movies.movieList.push( ...action.payload )
}
}
}
1.保证布局正确()
2.安装:$yarn add better-scroll
3.找到组件引入并使用(https://ustbhuangyi.github.io/better-scroll/doc/zh-hans/#better-scroll%20%E6%98%AF%E4%BB%80%E4%B9%88)
import BScroll from 'better-scroll'
mounted () {
// 发起数据请求
this.getMovies()
/* 以下代码实现的是 滚动 + 上拉加载 */
const bs = new BScroll('.page',{
click: true, // 开启页面可以点击
pullUpLoad: {
threshold: 50 // 离底部多少式触发
}
})
let count = 0
bs.on('pullingUp',() => {
// 写的是上拉加载事件
console.log('上拉')
// 上拉一次,完成数据请求
// this.getNewMovies( id )
const ids = _.chunk( this.movies.movieIds.slice( 12 ), 10 )
if ( count < ids.length ) {
this.getNewMovies( ids[ count ].join(',') )
bs.finishPullUp() // 结束上拉
}
if ( count == ids.length ) {
console.log('到底了')
bs.finishPullUp() // 结束上拉
return;
}
count ++
})
}
1.安装: $yarn add loadsh
2.页面中引用
import _ from 'loadsh'
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
// 如果你没有登录,项目中任何页面你都进不去-后台管理系统
// 自定义加载图标
Toast.loading({
message: '加载中...',
forbidClick: true,
loadingType: 'spinner'
});
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
如果加了scoped ,那么路径别名前加一个 ~
运行项目: n p m r u n s e r v e / npm run serve / npmrunserve/yarn serve
如果我们能把不同路由对应的组件分割成不同代码块,然后当路由被访问时才加载对应组件,这样就很高效。方法:结合Vue的异步组件和webpack的代码(注释语法)分割功能,实现路由的懒加载,效果如下,比如Home组件(routers.js)
const Home = () => import(/* webpackChunkName: "group-lucia" */ '@/views/home/index.vue')
激活属性(active-class)
使用vue内置提供的 router-link 组件来完成tabbar的跳转
<router-link
:to=" item.path "
active-class = "active"
>
<i class="fas" :class = "[ item.iconName ]"></i>
<span> {{ item.text }} </span>
</router-link>