Vue项目实战-vue2(移动端)_vue2项目-程序员宅基地

技术标签: 前端  vue.js  javascript  

Vue项目实战(移动端)#

相关资料#

  1. vue-cli脚手架: vue2脚手架
  2. vue3脚手架: vite
  3. vue官网: [介绍 — Vue.js
  4. vscode插件
    • vetur 必备工具
    • vue-helper 一些辅助功能
    • Vue VSCode Snippets 片段

(一) 创建项目#

01 安装vue-cli脚手架#

npm install -g @vue/cli

02 查看vue脚手架版本#

出现版本号表示成功

vue --version

03 创建一个新项目#

创建项目

vue create hello-world  // 1.创建项目

运行项目

cd hello-world  // 2.进入项目文件夹
npm run serve		// 3.运行项目

(二) 禁用Eslint#

// 根目录新增vue.config.js
module.exports = {
    lintOnSave: false
}

如果vue组件提示红色错误,如下图 

 解决办法: 文件 -> 首选项 -> 设置 然后输入eslint -> 选择Vetur -> 把√取消即可 

(三) devtool#

vue开发调试工具

  1. 下载 http://soft.huruqing.cn
  2. 添加到chrome扩展程序里

(四) 添加less支持#

  1. npm install less [email protected] --save-dev

  2. 在vue文件这样写即可, scoped表示样式只在当前文件有效, 不会影响其他组件

    ps: less-loader要安装6.0版本, 不然有兼容问题

    <style lang="less" scoped> 
    .box {
      .text {
        color: red;
      }
    } 
    </style>
    

(五) vue路由配置(背诵)#

(1)一个简单路由配置#

  1. npm i vue-router  安装路由插件
  2. 在src创建views文件夹, 创建各个模块的组件
  3. 在src内创建router文件夹, 新建index.js(代码如下)
  4. 在main.js里, 把router挂载到vue的实例
  5. 配置路由出口, 详见下方第(2)点router-view
  6. 使用router-link进行跳转, 详见下方第(3)点路由跳转
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router); 
// 路由数组
const routes = [
    {
        path: '/product',
        component: ()=>import('@/views/product/index.vue')
    },
    {
        path: '/cart',
        component: ()=>import('@/views/cart/index.vue')
    },
]

const router = new  Router({
    routes
})
export default router;
// main.js 代码
import Vue from 'vue'
import App from './App.vue'
import router from './router/index'

Vue.config.productionTip = false

new Vue({
  // 把router挂载到vue实例
  router,
  render: h => h(App),
}).$mount('#app')

(2) router-view#

  1. 路由出口
  2. 路由匹配到的组件将渲染在这里
  3. 在app.vue配置
<template>
  <div id="app"> 
    <!-- 路由出口 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {},
};
</script>

(3) 路由跳转#

// 方式一
<router-link to="/cart">cart</router-link>

// 方式二
this.$router.push('/cart');

(4) 子路由配置#

使用子路由进行模块路由配置,结构比较分明 比如我们的网站有商品模块,有列表页面和详情页面, 路由如下 /product   商品模块总路由 /prodcut/list   子路由 /product/detail   子路由

{
    path: '/product',
    component: () => import('@/views/product/index'),
    children: [
        {
            path: 'list',
            component: ()=>import('@/views/product/children/list')
        },
        {
            path: 'detail',
            component: ()=>import('@/views/product/children/detail')
        }
    ]
}

(5) active-class#

active-class是vue-router模块的router-link组件中的属性,用来做选中样式的切换;

  1. 只要路由中包含to里面的路由, 就能匹配到, 就会高亮, 比如: /product, /product/list, /product/detail都会使下面的第二个router-link高亮
  2. exact 表示精确匹配, 只有路由完全一样才能被匹配
<router-link to="/" active-class="on" exact>首页</router-link>
<router-link to="/product" active-class="on">product</router-link>
<router-link to="/cart" active-class="on">cart</router-link>
<router-link to="/my" active-class="on">my</router-link>
<router-link to="/order" active-class="on">order</router-link>

(6) history模式#

vue2配置方式

  1. vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

  2. 如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面

  3. 使用history需要后端支持, vue-cli创建的devServer可以支持

    const router = new VueRouter({
      mode: 'history',  // 默认hash
      routes: [...]
    })
    

vue3配置方式

const router = createRouter({ 
 	history: createWebHistory(),  // history模式
 	//history: createWebHashHistory(), // hash模式
  	routes
});     

(7) redirect重定向#

当访问 '/', 我们使用redirect使它默认跳到 '/product'

{
    path: '/',
    redirect: '/product'
},

(8) 404配置#

假如用户访问了一个没有的路由, 我们让它跳转到404页面

  {
    path: '*',
    component:()=>import('@/components/NotFound')
  }

(六) 父子组件通信(背诵)#

知识点(背诵):

  1. 父传子: 父组件通过(绑定)属性的方式传数据给子组件, 子组件使用props接收数据
  2. 子传父: 父组件在子组件上绑定一个自定义事件, 子组件通过$emit触发该自定义事件, 同时可以传入数据

1.父传子#

  • 父组件给子组件绑定属性, 属性的值是需要传递的信息
  • 子组件通过props接收父组件的信息
 // 例子1: 使用普通属性
// demo.vue
<template>
  <div>
    <h3>父组件</h3>
    <hr />
    <Son msg="hello world" username="张三"/>
  </div>
</template>

<script>
import Son from "./Son";
export default {
  components: {
    Son,
  },
};
</script> 

// Son.vue
<template>
  <div>
    <h4>子组件</h4>
    <p>msg: {
   { msg }}</p>
    <p>username: {
   { username }}</p>
  </div>
</template>

<script>
export default {
  props: ["msg", "username"],
};
</script> 

// 例子2: 使用绑定属性(可传变量)
// demo.vue
<template>
  <div>
    <h3>父组件</h3>
    <hr />
    <Son :msg="msg" :username="username" />
  </div>
</template>

<script>
import Son from "./Son";
export default {
  components: {
    Son,
  },
  data() {
    return {
        msg: '哈哈哈',
        username: '李四'
    };
  },
};
</script> 

// Son.vue
<template>
  <div>
    <h4>子组件</h4>
    <p>msg: {
   { msg }}</p>
    <p>username: {
   { username }}</p>
  </div>
</template>

<script>
export default {
  props: ["msg", "username"],
};
</script> 

父传子实践: 把首页拆分为多个组件 技巧: 如果某个部分只是做展示用, 尽量把它变成子组件

2. 子传父#

  1. 父组件在子组件上绑定一个自定义事件(事件名称我们自己定义的, vue本身是没有这个事件的)
  2. 父组件给自定义事件绑定一个函数, 这个函数可以接受来自子组件的数据
  3. 子组件使用$emit触发(调用)该事件, 并把数据以参数形式传给父组件
// 例子1: 一个简单的例子
// demo.vue
<template>
  <div>
    <h3>父组件</h3>
    <hr />
    <Son @aaa="say"/>
  </div>
</template>

<script>
import Son from "./Son";
export default {
  components: {
    Son,
  },
  data() {
    return { 
    };
  },
  methods: {
    say(data) {
      alert(data)
    }
  }

};
</script> 

// 子组件
<template>
  <div>
    <h4>子组件</h4>
    <button @click="$emit('aaa','我是子组件')">点击</button>
  </div>
</template>

<script>
export default {
  props: ["msg", "username"],
};
</script>  

(七) axios拦截器(背诵)#

  1. 对ajax请求进行拦截
    1. 在请求头添加token
  2. 对ajax响应数据进行拦截
    1. 统一处理请求失败的情况, 这样就不需要在每个组件里处理失败的情况
    2. 有些接口需要登录才能访问, 在没登录的情况下跳转到登录页面
import axios from "axios";
import Vue from "vue";
import { Toast } from "vant";
Vue.use(Toast);

const service = axios.create({
  baseURL: "http://huruqing.cn:3003",
  timeout: 50000, // 请求超时时间(因为需要调试后台,所以设置得比较大)
});

// request 对请求进行拦截
service.interceptors.request.use(
  (config) => {
    // 开启loading
    Toast.loading({
      message: "加载中...",
      forbidClick: true,
      loadingType: "spinner",
    });
    // 请求头添加token
    config.headers["token"] =
      "gg12j3h4ghj2g134kj1g234gh12jh34k12h34g12kjh34kh1g";
    return config;
  },
  (error) => {
    Promise.reject(error);
  }
);

// response 响应拦截器
service.interceptors.response.use(
  (response) => {
    Toast.clear();
    const res = response.data;
    if (res.code == 666) {
      return res;
    } else {
      // 成功连接到后台, 但是没有返回正确的数据
      Toast.fail(res.msg);
    }
  },
  (error) => {
    Toast.clear();
    // 跟后台连接失败
    Toast.fail("网络异常,请稍后再试");
  }
);

export default service;

(八) Sticky 粘性布局#

(九) 图片懒加载#

二、vue2.x进阶教程 | 清流

(十) 全局注册组件#

// 注册全局组件除了多了个template之外,其它跟平时写组件类似
// 在main.js,实例化vue组件之前执行以下代码
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">你打了我 {
   { count }} 次</button>'
})

// 在其他组件就可以使用
<template>
	<div>
  	<button-counter></button-counter>
  </div>  
</template>
// 改造checkbox, 官网例子
Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

// 然后就可以像下面这样来使用
<template>
  <div> 
   <base-checkbox v-model="flag"></base-checkbox>
		<p>{
   {flag}}</p>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      flag: false
    };
  },
}
</script>
// 另外需要在根目录的vue.config.js中开启运行时编译
module.exports = {
    runtimeCompiler: true
}

(十一) slot插槽#

元素作为承载分发内容的出口 一个内存插槽, 当内存插上之后,插槽就可以接收来自内存的信息, slot取名插槽含义也贴切, 在子组件配置插槽slot, 当父组件"插"信息进来的时候, 插槽slot就能接收到这个信息. slot插槽大大的扩展子组件的功能。 

1. vant有赞ui库中slot的例子#

<van-nav-bar title="标题" left-text="返回" left-arrow> 
   <p slot="right">
     <van-icon name="search" size="18" />
   </p>
</van-nav-bar>

2. 普通插槽#

// 父组件demo.vue代码
<template>
  <div>
    <h3>父组件</h3>
    <hr>
    <Son><button>按钮</button></Son>
  </div>
</template>

<script>
import Son from "./Son";
export default {
  components: {
    Son,
  }
};
</script> 

// 子组件Son.vue
<template>
  <div>
    <slot></slot>
  </div>
</template> 

3. 具名插槽#

// father.vue代码
<template>
  <div>
    <h3>这是父组件</h3>

    <Child>
      <header slot="header" style="background: yellow">这是头部</header>
      <footer slot="footer" style="background: green;">这是底部</footer>

      <div style="border:1px solid;">
        <button>a</button>
        <button>b</button>
        <button>c</button>
        <button>d</button>
      </div>
    </Child>
  </div>
</template>

<script>
import Child from "@/components/Child";
export default {
  components: {
    Child
  }
};
</script>

接收父组件带 slot="footer" 的内容
接收不带slot="xxx" 的内容

// Child.vue代码
<template>
  <div style="margin-top: 30px;background: gray;height: 200px;">
    <h5>这是子组件</h5>
		<!--接收父组件带 slot="header" 的内容-->
    <slot name="header"></slot>
		<!--接收父组件带 slot="footer" 的内容-->
    <slot name="footer"></slot>
		<!--接收剩余内容-->
    <slot></slot>
  </div>
</template>

自定义组件

// demo.vue
<template>
  <div> 
    <NavBar title="首页" @click-left="clickLeft" @click-right="clickRight"></NavBar>
  </div>
</template>

<script> 
import NavBar from './Nav-Bar.vue'
export default {
    components: {
      NavBar
    },

    methods:{
      clickLeft() {
        alert('左边被点击了'); 
      },
      clickRight() {
        alert('右边被点击了')
      }
    }
}
</script>


// Nav-Bar.vue
<template>
    <div class="nav flex jc-sb pl-15 pr-15 bg-fff aic">
      <p class="blue flec aic" @click="$emit('click-left')">
        <van-icon name="arrow-left" />
        <span>返回</span>
      </p>
      <p>{
   {title?title:'标题'}}</p>
      <slot name="right"> <span  class="blue" @click="$emit('click-right')">按钮</span></slot>
    </div>
</template>

<script>
export default {
  props: ['title']
}
</script> 

<style lang="less">
.nav {
  height: 50px;
  .blue {
    color: #1989fa;
  }
}

</style>

(十二) 使用ui库需要关注的三点#

以vant 的导航栏组件van-nav-bar为例

  1. 属性, 该组件提供了哪些绑定属性
  2. 事件, 该组件提供了哪些事件
  3. 插槽, 该组件提供了哪些插槽

(十三) 三种路由传参方式(背诵)#

知识点:

  1. 通过params传参, 使用$route.params接收参数
  2. 动态路由传参, 使用$route.params接收参数
  3. 通过query传参, $route.query接收参数

注意: router和route不是一回事 ​

1.通过name+params传参#

// 1.配置路由的时候添加name
  {
        path: "detail",
        name: 'product-detail',
        component: () => import("@/views/order/children/detail"),
  },


// 2.跳转
 this.$router.push({
        // 要跳转到的路由名称
        name: 'product-detail',
         params: { productId: '123' }
      })

// 3.接收参数
this.$route.params.productId

2.动态路由传参#

// 1.配置路由
{
  path: "detail/:productId", 
  component: () => import("@/views/product/children/detail.vue"),
},
  
// 2. 跳转
this.$router.push('/product/detai/22222')
<router-link to="/product/detail/333333">传参</router-link>

  
// 3.接收参数
 created() {
    let params = this.$route.params;
    console.log('params',params); 
  },
  

3.通过path+query传参#

// 带查询参数,query传参会把参数拼接到地址栏,变成 /register?plan=aaa, 使用了path,参数不能通过params传递
this.$router.push({ path: '/register', query: { plan: 'aaa' }})
// 获取参数
this.$route.query;

(十四) 模拟数据#

  1. 文档地址:  json-server - npm
  2. npm i json-server -g    //全局安装
  3. 根目录创建db.json
  4. 启动json-server
json-server --watch db.json
// db.json
{
  "posts": [
    { "id": 1, "title": "json-server", "author": "typicode" }
  ],
  "comments": [
    { "id": 1, "body": "some comment", "postId": 1 }
  ],
  "profile": { "name": "typicode" }
}
  1. 访问接口
http://localhost:3000/posts/1
  1. 将命令添加到package.json, 可以使用 npm run json 启动项目
 "scripts": {
    "json": "json-server --watch db.json" 
  },

(十五) 计算属性computed和属性观察watch#

vue中computed和watch区别 - 简书

  1. computed的作用
  2. watch的作用
  3. computed和watch的区别
// computed
<template>
  <div>
    <p>姓: {
   { xing }}</p>
    <p>名: {
   { ming }}</p>

    <p>姓名: {
   { xingming }}</p>

    <button @click="change">修改xing</button>
  </div>
</template>  

<script>
export default {
  data() {
    return {
      xing: "张",
      ming: "无忌",
    };
  },

  // 计算属性
  computed: {
    // xingming这个属性是由xing属性和ming计算得来
    xingming() {
      return this.xing + this.ming;
    },
  },

  methods: {
    change() {
      this.xing = "李";
    },
  },
};
</script>

(十六) vuex(背诵)#

(1) 普通对象 VS vuex创建的对象#

  1. 普通对象
    1. 创建对象
    2. 定义对象的属性
    3. 修改对象的属性
    4. 读取对象属性
  2. vuex
    1. 创建仓库
    2. 定义状态
    3. 修改状态
    4. 读取状态

(2) 相关概念#

  1. 概念vuex是什么: 创建一个仓库, 然后在仓库里定义若干状态, 并且管理这些状态. Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
  2. vuex有哪几个核心概念, 都是用来做什么的
    1. state 定义状态
    2. getters 派生状态
    3. mutation 修改状态(同步)
    4. action 修改状态(异步)
    5. module 模块化
  3. 如何使用vuex进行跨组件通信
  4. vuex持久化

// getters派生状态


// 1.  在 src/store/index.js
state: {
    token: "",
    username: "张三",
    age: 100,
    phone: "123456789",
  },

  getters: {
    // 派生状态
    str(state) {
      return `我叫${state.username},我的年龄是${state.age}`
    }
  },
    
// 2. 在组件里使用
    
<template>
	<div> 
  {
   {str}}
  </div>  
  
</template>    
    
import {mapGetters} from 'vuex';
export default {
 computed:{
 		...mapGetters(['str'])
 } 
}



// action 修改状态(异步)

  1. 定义状态
  2. 定义mutation, 通过mutation来修改状态
  3. 定义action , 通过action来提交(commit)mutation
  4. 用户派发action
import Vue from "vue";
import Vuex from "vuex";
import $http from '@/utils/http';
// 导入持久化插件
import createPersistedState from "vuex-persistedstate";

Vue.use(Vuex);
// 创建仓库
const store = new Vuex.Store({
  plugins: [createPersistedState()],
  // 1.定义状态
  state: {
    token: "",
    phone: "123456789",
    username: "张三",
    age: 100,
  },

  getters: {
    // 派生状态
    str(state) {
      return `我叫${state.username},我的年龄是${state.age}`
    }
  },


  // 2.定义mutaion
  mutations: {
    // 修改token
    set_token(state,payload) {
      state.token = payload
    },

    // 修改phone的状态
    set_phone(state, payload) {
      state.phone = payload;
    },
    /**
     * 定义修改username的muation
     * @param {*} state 状态
     * @param {*} payload 传入的新数据
     */
    set_username(state, payload) {
      state.username = payload;
    },

    // 定义修改age的mutation
    set_age(state, payload) {
      state.age = payload;
    },
  },

  // 3.定义action
  actions: {
    LOGOUT(store,payload) {
      $http.post('/user/logout').then(res=> {
          // 清除token和phone
          store.commit('set_token','');
          store.commit('set_phone','');
      })
    }
  } 
});

export default store;


// 4.退出登录时派发action
 <p class="red" @click="logout2">退出登录</p>

methods: {
	 logout2() {
      this.$store.dispatch('LOGOUT');
       this.$router.push('/my');
    },
}

// 模块化

// 1.定义模块的state getters mutaions actions
// src/store/modules/cart.js
export default {
    state: {
       cartNum: 100 
    },

    getters: { },
    mutaions: { },
    actions: {}
}
// src/store/modules/type.js
export default {
    state: {
       aaa: 333 
    },

    getters: { },
    mutaions: { },
    actions: {}
}


// 2.合并模块
import cart from './modules/cart';

const store = new Vuex.Store({
  modules:{
    cart,
    type, 
  },
}
                             
// 3.使用(在任何一个组件内)
<template>
	<div>
  	  {
   { $store.state.cart.cartNum }}
      {
   {$store.state.type.aaa}}                            
  </div>                             
</template>                             
                             


(3) vuex应用#

  • 创建仓库
    1. 需要先安装vuex npm i vuex --save
    2. 创建仓库
    3. 挂载仓库
// 1. src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

// 创建仓库
const store = new Vuex.Store({

});

export default store;


// 2. 挂载到根实例 /src/main.js
import router from "./router/index";
import store from './store/index';

Vue.use(Vant);  

Vue.config.productionTip = false;
new Vue({
  store,
  router,
  render: (h) => h(App),
}).$mount("#app");
  • 定义状态
const store = new Vuex.Store({
  // 定义状态
  state: {
    username: "张三",
    age: 100,
  },
});
  • 获取状态
    1. 直接获取 this.$store.state.username
<template>
  <div>
    <p>username: {
   {$store.state.username}}</p>
  </div>
</template>   
<script> 
export default {
    created() {
      console.log(this.$store.state);
    } 
};
</script>
  1. 通过mapState获取, mapState是vuex提供的方法, 可以让我们更方便的获取属性
<template>
  <div>
    <p>username: {
   {username}}</p>
    <p>age: {
   {age}}</p>
  </div>
</template>  

<script>
import {mapState} from 'vuex';
export default { 
    computed: {
      ...mapState(['username','age'])
    }
};
</script>
  • 修改状态: 通过mutation进行修改
    • 修改状态只能通过mutation来修改, 不可以直接修改
    • mutation只支持同步操作
  • 步骤:
    • 定义mutation
    • 提交mutation
// 1.定义mutation
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
// 创建仓库
const store = new Vuex.Store({
  // 定义状态
  state: {
    username: "张三",
    age: 100,
  },
  // 定义mutaion
  mutations: {
      /**
       * 定义修改username的muation 
       * @param {*} state 状态
       * @param {*} payload 传入的新数据
       */
      set_username(state,payload) {
        state.username = payload;
      },

      // 定义修改age的mutation
      set_age(state,payload) {
        state.age = payload;
      }
  } 
});

export default store;


// 2. 提交mutaion
<template>
  <div>
    <p>username: {
   {$store.state.username}}</p>
    <button @click="change">修改状态</button>
  </div>
</template>   
<script> 
export default {  
    methods: {
      change() {
        // 提交mutation,参数1 mutation的名称, 参数2 新的数据
        this.$store.commit('set_username','李四'); 
      }
    }
};
</script>

项目应用

  1. 定义一个状态 phone, 值为空
  2. 登录成功之后, 修改phone的状态
  3. 在个人中心页面, 获取phone状态
    1. 若有phone, 显示phone
    2. 若没有, 就显示立即登录

vuex持久化

  1. 安装插件 npm i vuex-persistedstate -S
  2. 应用插件
import Vue from "vue";
import Vuex from "vuex";
// 导入持久化插件
import createPersistedState from "vuex-persistedstate"; 
Vue.use(Vuex); 
an 
const store = new Vuex.Store({
  plugins: [createPersistedState()],
})

(十七) 浏览器缓存cookie,sessionStorage,localStorage#

(1) 对比

  1. 三者都是浏览器缓存,可以将数据存储在浏览器上, 其中后两者是html5的新特性
  2. cookie存储容量较小,一般浏览器4KB, 后两者5M
  3. sessionStorage:临时存储, 浏览器关闭就销毁, localStorage: 永久存储, 销毁需要手动销毁

(2) 操作

  1. cookie使用相关js库 _js_-_cookie_
  2. sessionStorage,localStorage使用其自带方法
// 存储数据
localStorage.setItem(key,value);  // 比如:localStorage.setItem('username','张三')
// 获取数据
localStorage.getItem(key);        // 比如: localStorage.getItem('username');
// 清除数据
localStorage.clear();

(十八) token(令牌)和session(会话)#

相同点: 两者都是用来识别用户的

  1. session会话, sessionId
    1. 对于特定接口, 前端需要登录才能访问, 所以第一次访问时需要登录, 登录成功, 服务器会返回一个sessionId
    2. 下次前端再访问同一个接口的时候, 把sessionId带上(cookie), 这样服务器就能识别是谁在访问, 如果这个人已经登录过, 就不再需要再登录, session一般设有效期
  2. token令牌, 或叫同行证
    1. 前端在登录成功时, 服务器会把用户的相关信息加密, 得到一个密文, 这就是token, 返回给前端
    2. 前端再次访问接口时, 把token带上, 服务器端收到token就对它进行解密, 得到用户信息

项目应用

  1. 在vuex里定义token状态和相关的mutation
  2. 在登录成功的时候, 把token存入vuex
  3. 在axios的拦截器里, 把token放入请求头, 这样, 每次发请求的时候, 都会自动带上token
// 1.  在vuex里定义token状态和相关的mutation
state: {
    token: "",
    username: "张三",
    age: 100,
    phone: "123456789",
  },
  // 定义mutaion
  mutations: {
    // 修改token
    set_token(state,payload) {
      state.token = payload
    },
  }
  
  
  // 2. 在登录成功的时候, 把token存入vuex
   $http.post('/user/login',data).then(res=> {
        // 把手机号码存入store, 修改phone状态
        this.$store.commit('set_phone',this.phone);
        // 把token存入store
        this.$store.commit('set_token',res.result.token);
        // 从哪里来回哪里去
        this.$router.go(-1); 
      })

  // 3. 在axios的拦截器里, 把token放入请求头, 这样, 每次发请求的时候, 都会自动带上token
  
  
import axios from "axios";
import Vue from "vue";
import { Toast } from "vant";
// 导入store
import store from '@/store/index';
Vue.use(Toast);
  
  
// request 对请求进行拦截
service.interceptors.request.use(
  (config) => {
    // 获取token
    let token = store.state.token;  
    // 开启loading
    Toast.loading({
      message: "加载中...",
      forbidClick: true,
      loadingType: "spinner",
    });
    // 请求头添加token
    config.headers["user-token"] = token;
    return config;
  },
  (error) => {
    Promise.reject(error);
  }
);

(十九) vue过滤器#

作用: 格式化数据

// 组件内的过滤器
<template>
  <div>
      {
   {num | f}}
  </div>
</template>   

<script>
export default {
  data() {
    return {
      num: 10
    }
  }, 
  filters: {
    f(num) {
      return Number(num).toFixed(2);
    }
  }
}
</script>

// 全局过滤器
Vue.filter('fMoney', (money)=> {
  let num = money/100;
  return num.toFixed(2);
})
new Vue({})

// 定义好全局过滤器后, 组件内可以直接使用
<template>
  <div>
      {
   {num | fMoney}}
  </div>
</template>   

<script>
export default {
  data() {
    return {
      num: 1000
    }
  }, 
}
</script>

(二十) 微信支付-轮询和websocket#

(1) 微信支付流程#

  1. 用户点击提交订单, 商户(服务器端)创建订单, 并返回订单信息和支付二维码给用户
  2. 用户扫码支付(货值调起微信支付)
  3. 支付平台收到钱后, 返回支付信息给用户, 同时通知商户(服务器端)已收到用户的钱
  4. 商户(服务器端)修改订单的状态
  5. 用户(web端)获取支付结果, 得到结果后做相应操作
    1. 轮询方式
    2. websocket

(2) 获取支付结果的两种方式#

获取支付结果, 可以使用轮询或者websocket

  1. 轮询, 定时给服务器请求, 询问结果, 直到有结果为止, 轮询不需要服务器特别的支持
  2. websocket, 前端只需跟后台建立连接即可(长连接), 有了结果服务器可以给前端主动推送信息, websocket是长连接, 而http请求是一次性连接, websocket需要服务器端创建socket接口, 很多网站的客服服务就是使用websocket做的
// 轮询
<template>
  <div class="payment pay"></div>
</template>

<script>
export default {
  data() {
    return {
      timer: null,
      orderId: 'sdfasdfasdfasdfasdfasdfas'
    };
  },

  created() {
    this.waitResult();
  },

  beforeDestroy() {
    // 销毁定时器
    clearInterval(this.timer);
  },

  methods: {
    async waitResult() {
      // 创建定时器
      this.timer = setInterval(async () => {
        let res = await this.$axios.post("/order/detail", {
          orderId: this.orderId,
        });
        if (res.result.orderStatus === "01") {
          clearInterval(this.timer);
          // 支付成功, 返回首页
          this.$router.push("/");
        }
      }, 2000);
    },
  },
};
</script> 
// webSocket
<template>
  <div>{
   {result}}</div>
</template>

// webSocket
<template>
  <div>{
   {result}}</div>
</template>

<script>
export default { 
  data() {
    return {
      result: ''
    }
  },

  created() {
    this.connect();
  },
  methods: {
    connect() {
      this.result = '等待支付结果...';
      // 跟后端建立连接
      var ws = new WebSocket("ws://huruqing.cn:3003/socket");
      // onopen连接结果
      ws.onopen = () => {
        console.log("连接成功");
      };
      // 等待后端推送信息
      ws.onmessage = (res) => {  
        this.result = res.data;
      };
    },
  },
};
</script> 

(二十一) 进入组件, 滚动条不在顶部的问题#

解决办法

// router/index.js
const routes = [...];
const router = new Router({
  mode: "history",
  scrollBehavior: () => ({
    y: 0
  }),
  routes
});

(二十二) keep-alive(背诵)#

问题: 用户从列表的第3页, 点击某个商品进入了商品详情, 当用户点击返回的时候, 默认会返回到列表页的第一页而不是第3页, 这样的体验很不好, 所以我们希望可以回到列表页的原来位置, 这样的用户体验会比较好. 分析: 之所以会回到第一页, 是因为返回到列表页的时候, 组件会重新创建, 从新执行created方法, 所以页面页重新渲染 解决: 使用keep-alive可以缓存组件的状态, 具体做法: (1) 对列表页使用keep-alive, 使其即使离开了组件, 也不会销毁 组件挂载完毕的时候绑定滚动事件, 记录滚动的位置 (2) 从详情页返回的时候, 滚动的原来的位置(在activated生命周期) **注: **

  1. 被keep-live包裹的组件会被缓存
  2. 使用keep-alive的组件crated和mounted只会执行一次
  3. 离开组件会触发deactivated生命周期(只有被缓存的组件才有的生命周期)
  4. 进入组件会触发activated生命周期
// 方法1 APP.vue
<template>
  <div id="app"> 
    <keep-alive>
      <router-view></router-view>
    </keep-alive>
  </div>
</template> 

// 方法2, 给路由配置keepAlive属性
// (1) /router/index.js
 {
    path: "/product",
    component: () => import("@/views/product/index.vue"),
    redirect: "/product/list",
    children: [
      {
        path: "list",
        // 缓存次组件
        meta: {
          keepAlive: true,
          tittle: '列表'
        },
        component: () => import("@/views/product/children/list2.vue"),
      },
      {
        path: "detail/:productId",
        component: () => import("@/views/product/children/detail.vue"),
      },
    ],
  },

// APP.vue
<template>
  <div id="app"> 
      <!-- 渲染需要缓存的组件 -->
     <keep-alive>  
        <router-view v-if="$route.meta.keepAlive"></router-view>
     </keep-alive>

      <!-- 渲染不需要缓存的组件 -->
      <router-view v-if="!$route.meta.keepAlive"></router-view>
  </div>
</template> 

// 上面需求的实现
(1) 在mounted绑定window.scroll事件, 滚动的时候保存滚动条的位置
(2) 返回时候, 重新滚动到原来保存的位置 

  mounted() {
    window.addEventListener('scroll',()=>{  
      // 保存滚动条位置
      if (window.scrollY>0) {
          this.scrollHeight = window.scrollY;
      }
    },false);
  },  

  // 进入组件
  activated() { 
    // 滚动到最初的位置
    setTimeout(()=> {
      window.scrollTo(0,this.scrollHeight); 
    },0)
  },

(二十三) 配置环境变量#

项目开发的时候, 一般会有多个环境, 比如开发环境, 测试环境, 生产环境, 我们调用接口的时候, 不同环境调用不同的接口, 所以要配置环境, ,方便访问。

// utils/http.js 核心代码

let env = process.env.NODE_ENV;
let baseURL;
// 开发环境
if (env === "development") {
  baseURL = "http://localhost:3003";
} else {
  baseURL = "http://huruqing.cn:3003";
}

const service = axios.create({
  // 如果换了新的项目, 需要更换为新的接口地址
  baseURL: baseURL,
  timeout: 50000, // 请求超时时间(因为需要调试后台,所以设置得比较大)
});

(二十四) rem移动端适配#

(1) 元素单位有哪些:#

(2) rem和根标签字体大小的关系#

// rem例子 demo1.html
<!DOCTYPE html>
<html lang="en" style="font-size: 100px;">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style> 
        div{
            width: 1rem;
            height: 1rem;
            background-color: gray;
        }
    </style>
</head>
<body>
    <div>

    </div>
</body>
</html>

// rem例子 demo1.html
<!DOCTYPE html>
<html lang="en" style="font-size: 112px;">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style> 
        div{
            width: 1rem;
            height: 1rem;
            background-color: green;
        }
    </style>
</head>
<body>
    <div>

    </div>
</body>
</html>

(3) 移动端rem适配原理#

  1. 设置一个设备参考值(比如iPhone6)
  2. 跟据设备宽度等比缩放根标签字体大小

(4) vue项目配置rem#

  1. 安装插 npm i amfe-flexible --save
  2. 在main.js导入插件 import 'amfe-flexible'
  3. px自动转rem
    1. 安装插件 npm i [email protected]
    2. 在/vue.config.js添加px2rem插件,把项目中的px转为rem
const pxtorem = require("postcss-pxtorem");

module.exports = {
  css: {
    loaderOptions: {
        // 后处理器配置
      postcss: {
        plugins: [
          // 把px转为rem
          pxtorem({
            rootValue: 37.5,
            propList: ["*"]
          })
        ]
      }
    }
  }
};
  1. 插件会修改html和body的字体大小, 而字体会继承, 所以要重新设置body的font-size为合适的字体大小

(二十五) mixin(混入)#

mixin 其实是一个对象,里面的结构大致跟普通组件的 script 里面的一样,有 data 属性,钩子函数和方法等 混入 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。

1.组件内混入#

(1) 混入对象的生命周期先执行 (2) data里的状态若有重名, 取的是组件里的状态

// mixin.js
export default {
    data: function() {
      return {
        username: "huruqing",
        age: 100
      };
    },

    created() {
        console.log('这是混入对象')
    },

    methods: {
        say() {
            console.log('hahahhahahha');
        }
    }
  };


// demo.vue
<template>
  <div>
    <p>{
   {username}}</p>
    <p>{
   {msg}}</p>
    <p>{
   {age}}</p>
  </div>
</template>

<script>
import mixin from './mixin'
export default {
  mixins:[mixin],
  data() {
    return {
      username: '张三',
      msg: 'hahahahahahha'
    }
  },

  created() {
    console.log('组件的created');
    this.say();
  }
}
</script> 

2.全局混入#

// mixin.js
export default {
    methods: {
        loadingStart() {
            this.$toast.loading({
                message: '加载中...',
                forbidClick: true,
                duration:0
              });
        },
        loadingFinish() {
            this.$toast.clear();
        }
    }
}

// main.js,这个代码放在Vue.use(Vant)之后
import mixin from '@/utils/mixin';
Vue.mixin(mixin);

// 其他组件就可以直接使用下面代码来显示loading
this.loadingStart(); 

(二十六) watch监听对象#

  1. 普通的监听方法, 对对象无效
  2. watch的参数
    1. handler 监听器(有改变就执行
    2. immediate 马上执行
    3. deep 监听引用数据类型
<template>
  <div>
    <input type="text" v-model="obj.username" />
    <p>{
   { obj.username }}</p>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      obj: {
        username: "张三",
      },
    };
  },

  watch: { 
    obj: {
      // 发生改变时执行的函数
      handler(newObj) {
        console.log(newObj.username);
      },
       // 首次绑定watch就执行
      immediate: true,
      // 深层监控,不设置,引用数据类型监控不到
      deep: true
    }
  },
};
</script>

(二十七) props检查类型#

// demo.vue
<template>
  <div>
      <!-- 不传参数 -->
    <Son/> 
    <!-- 传了一个字符串 -->
    <!-- <Son :msg="'他是张三'" />  -->
    <!-- 传了一串数字 -->
    <!-- <Son :msg="22222"/> -->
  </div>
</template>

<script>
import Son from "./Son.vue";
export default {
  components: {
    Son,
  } 
};
</script> 

// Son.vue
<template>
  <div>{
   {msg}}</div>
</template>

<script>
export default {
    // props: ['msg']
    props:{
        msg: {
            type:String,
            default: 'hello'
        }
    }
}
</script> 

(二十八) ref获取dom节点和子组件实例#

  1. ref可以获取原生dom节点和子组件实例
<template>
  <div>
    <span ref="demo">ref例子</span> 
    <button @click="handleClick">点击</button>
    <hr />
    <Son ref="son" />
  </div>
</template>

<script>
import Son from "./Son.vue";
export default {
  components: {
    Son,
  },
  methods: {
    handleClick() {
      console.log(this.$refs.demo.innerText);
      console.log(this.$refs.son.msg);
    },
  },
};
</script> 

// Son.vue
<template>
    <div>
        {
   {msg}}
    </div> 
</template>

<script>
export default { 
    data() {
        return {
            msg: 'hello',
            title: '2222222'
        }
    }
}
</script>
  1. 应用
// 父组件控制子组件的显示和隐藏(子组件无状态)
// demo.vue
<template>
  <div>
    <button @click="show = !show">点击</button>
    <hr>
    <Son :show="show" />
  </div>
</template>

<script>
import Son from "./Son.vue";
export default {
  components: {
    Son,
  },
  data() {
    return {
      show: true,
    };
  },
};
</script> 

// Son.vue
<template>
  <div v-if="show">
      <p>子组件内容</p>
      <p>子组件内容</p>
      <p>子组件内容</p>
      <p>子组件内容</p>
  </div>
</template>

<script>
export default {
    props: ['show'] 
}
</script> 
// 父组件控制子组件的显示和隐藏(子组件有状态)
<template>
  <div> 
    <button @click="$refs.aaa.show=!$refs.aaa.show">显示弹窗</button>  
    <Alert ref="aaa"/>
  </div>
</template>

<script> 
import Alert from './Alert.vue'

export default {  
  components: {
    Alert
  }, 
};
</script>


// Son.vue(假设子组件是别人设计的组件, 多处地方在使用, 所以并不适宜去改动, 不然容易改出bug, 子组件有个show的状态来控制其显示和隐藏
<template>
  <div v-if="show">
      <p>{
   {msg}}</p>
      <p>{
   {msg}}</p>
      <p>{
   {msg}}</p>
  </div>
</template>

<script>
export default { 
  data() {
    return {
      show: true,
      msg: 'hello vue'
    }
  }
}
</script> 

(二十九) nextStick#

修改了数据之后, dom节点并不会立马更新, dom节点的更新是异步的, 想要拿到更新后的dom需要使用nextStick

<template>
  <div>
    <li ref="aa">{
   { count1 }}</li>
    <li ref="bb">{
   { count2 }}</li>
    <li ref="cc">{
   { count3 }}</li>
    <button @click="handleClick">修改数据</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count1: 0,
      count2: 0,
      count3: 0,
    };
  },

  methods: {
    handleClick() {
      this.count1 = 1;
      // this.count1=1;执行后dom并不会立即更新,dom节点的更新是异步的
      console.log(this.$refs.aa.innerHTML);  // 0
      // 当dom节点更新完毕, 会立即调用nextStick里的回调函数
      this.$nextTick(() => {
        console.log(this.$refs.aa.innerHTML);
      });
      this.count2 = 2;
      this.count3 = 3;
    },
  },
};
</script> 

(三十) 配置跨域和模拟数据#


// 1. 在根目录新建mock文件夹
// 2. 添加/category/all.json和 /product/getBanners.json, json的数据就根据接口文档进行模拟
// 3. vue.config.js里配置
devServer: {
    // 代理
    proxy: {
      // 只要请求地址有'api'都会匹配上
      "/api": {
        target: "http://81.71.65.4:3003",
        ws: true,
        // 允许跨域
        changeOrigin: true,
        pathRewrite: {
          "^/api": "", //通过pathRewrite重写地址,将前缀/api转为/
        },
      },
    },
    before(app) { 
      // 模拟接口数据, 前面都加上了api是为了跨域设置的需要  
      // 分类列表
      app.get("/api/category/all", (req, res) => { 
        res.json(require('./mock/category/all.json'));
      });
      // banner列表
      app.get("/api/product/getBanners", (req, res) => { 
        res.json(require('./mock/product/getBanners.json'));
      });

    },
  },
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_57860459/article/details/123332349

智能推荐

Python 入门的60个基础练习_练习python基础语法-程序员宅基地

文章浏览阅读4.2w次,点赞329次,收藏2.7k次。Python 入门的60个基础练习_练习python基础语法

iOS6和iOS7代码的适配(2)——status bar_ios7 statusbar-程序员宅基地

文章浏览阅读1w次。用Xcode5运行一下应用,第一个看到的就是status bar的变化。在iOS6中,status bar是系统在处理,应用_ios7 statusbar

gdb调试时No symbol "var" defined in current context && No Register_no registers调试显示-程序员宅基地

文章浏览阅读2.1k次。问题描述:,在gdb调试程序输出变量:p var,会提示No symbol "var" in current context.原因:程序编译时开启了优化选项,那么在用GDB调试被优化过的程序时,可能会发生某些变量不能访问,或是取值错误码的情况。这个是很正常的,因为优化程序会删改程序,整理程序的语句顺序,剔除一些无意义的变量等,所以在GDB调试这种程序时,运行时的指令和你所编写指_no registers调试显示

IDGeneratorUtil 主键id生成工具类_idgeneratorutils.generateid()-程序员宅基地

文章浏览阅读3.4k次。import java.util.Random;import org.drools.util.UUIDGenerator;/** * * * 类名称:GenerateIdUtil * 类描述: 主键生成工具类 * @author chenly * 创建时间:Jul 10, 2012 8:10:43 AM * 修改人: * 修改时间:Jul 10, 2012 8..._idgeneratorutils.generateid()

关于汇编 BX 和 BLX 跳转指令_汇编blx-程序员宅基地

文章浏览阅读5k次。BX:跳转到寄存器reg给出的目的地址处,如:BX R2BLX:跳转到寄存区reg给出的目的地址处并将返回地址存储到LR(R14)使用这两个指令时有一点特别需要注意:跳转的目的地址必须是奇数,若不是奇数则在后面加1,如某函数的起始地址是0x80000f00,则要跳转到此函数则应该跳转到0x80000f01处!否则会进入硬件错误中断!..._汇编blx

前端vue,打包整合进后端springboot的resources里面后,运行只要刷新就报404_前端项目放入resource-程序员宅基地

文章浏览阅读2.6k次,点赞2次,收藏4次。vue打包后,其实就剩index.html和一堆静态资源,页面的加载和替换都是通过刷新index.html种的dom来实现的(应该是这样,可能表述不是很好),所以做个重定向就可以了。(博主是这么解决的,网上还有很多人是各种路径错误,大家可以尝试下自己是哪个原因)import org.springframework.boot.web.server.ConfigurableWebServerFa..._前端项目放入resource

随便推点

添加远程github仓库时报错 Warning: Permanently added the RSA host key for IP address 52.74.223.119_cmd warning: permanently added-程序员宅基地

文章浏览阅读9.7k次。1.问题展示2.解决方案1.任意窗口, 打开git bash2.命令行界面, 输入cd C:3.cat ~/.ssh/id_rsa.pub正常下面应该显示一大串公钥如果没有,显示如下图, 则进行下一步, 创建公钥4.创建公钥, 输入 ssh-keygen5.然后一直下一步, 直到出现6.再次输入cat ~/.ssh/id_rsa.pub下面一大串数字便是公钥,复制这些字符串, 打开github, 点击头像, 打开settings, 打开SSH and GPG Keys_cmd warning: permanently added

SQL*Plus 使用技巧1-程序员宅基地

文章浏览阅读154次。[code="java"]1. SQL/Plus 常用命令 a. help [topic] 查看命令的使用方法,topic表示需要查看的命令名称。 如: help desc; b. host 该命令可以从SQL*Plus环境切换到操作系统环境,以便执行操作系统命名。 c. host [command] 在sql*plus环境中执行操作系统命令,如:host notepad.exe..._sql+plus的使用方法

域控服务器搭建与管理论文,校园网络服务器的配置与管理 毕业论文.doc-程序员宅基地

文章浏览阅读441次。该文档均来自互联网,如果侵犯了您的个人权益,请联系我们将立即删除!**学校毕 业 论 文**学校园网络服务器的配置与管理姓 名: **学 号: **指导老师:系 名:专 业: 计算机网络技术班 级:二0一一年十二月十五日摘 要随着网络技术的不断发展和Internet的日益普及,许多学校都建立了校园网络并投入使用,这无疑对加快信息处理,提高工作效..._服务器配置与应用论文

mysql单实例多库与多实例单库_数据库单实例和多实例-程序员宅基地

文章浏览阅读1k次。一、单实例多库:一个mysql实例,创建多个数据目录。规划:实例路径:/usr/local/mysql数据目录路径:(1)/usr/local/mysql/data(2)/usr/local/mysql/data2步骤:安装mysql。配置my.cnf文件。初始化各个数据库。用mysqld_multi启动。1、安装mysql。平常安装。2、m..._数据库单实例和多实例

MFC解决找不到MFC90.DLL的问题_microsoft v90.debugmfc-程序员宅基地

文章浏览阅读6.3k次。今天装了第三方的MFC软件库Xtreme ToolkitPro v15.0.1,听说搞MFC的人都知道它的强大,我刚学习,所以装了一个,然后想运行一下它自带的例子看看。出现一个“找不到mfc90.dll“的问题,百度一下,记录如下:vs2008已经打过sp1补丁,编译C++程序会提示找不到mfc90.dll文件的错误,但是如果是release版的话就能正常运行csdn看到解决方案,粘贴_microsoft v90.debugmfc

XeLaTeX-中文排版解决方案_latex 中文排版 texlive-程序员宅基地

文章浏览阅读2.1k次。以前使用CJK进行中文的排版,需要自己生成字体库,近日,出现了XeTeX,可以比较好的解决中文字体问题,不需要额外生成LaTeX字体库,直接使用计算机系统里的字体,本文以在Linux下为例说明XeTeX的使用。操作系统: UbuntuTeX:除了texlive包外,还需要安装的包是texlive-xetex。字体:可以使用fc-list查看你自己的字体库,注意字体的完整名称,在XeTe..._latex 中文排版 texlive