前端 | vue2.0

vue2.0

1.基本步骤

导入vue.js

const vm = new Vue()

vm中{

​	el 控制哪个属性,

​	data 数据节点,

​	methods 页面上的处理函数

}

MVVM对应关系

	M-model(data指向的数据源)

​	V-view(视图,el指定控制的区域)

​	VM-viewmodel(核心,vue的实例对象)

2.指令

插值表达式 {{}}

v-bind 简写--冒号: 插值只能用于内容节点,这个可以用于属性节点 -- 单向绑定,数据-->视图

v-on 简写--@

v-if & v-else (v-if 不利于频繁切换,常用;v-else类似display)

v-for :key (循环创建相似ui结构,key-常用id,不可重复,字符串或者数字)

v-model(双向绑定;修饰符:.umber, .trim, .lazy)

3.过滤器

3.1基本语法

定义filters节点

函数要有return值

形参即为管道符前的值

3.2私有&全局过滤器

/*
      全局过滤器
      独立于实例之外
      参数1--函数名
      参数2--函数具体
      
      名字一致--就近原则--调用私有
    */
Vue.filter("capitalize", (val) => {
    return '全局用法---' + val.charAt(0).toUpperCase() + val.slice(1) + '---全局用法';
});

注意: filter 顺序问题: 先定义过滤器, 再新建 vue 实例

3.3传参

msg|fn(c1,c2) ---- fn(msg, c1, c2)

4.侦听器

4.1使用

watch节点

watch: {
    // 侦听器,本质上是一个函数,要监听哪个数据的变化,就将其数据名定义为方法名
    // 参数--新值在前,旧值在后
    // 用法,eg.用户名是否被占用 
    username(newVal, oldVal) {
        // 判断是否为空
        if (newVal === '') {
            return
        }
        // 调用ajax请求,判断是否被占用
        $.get('https://www.escook.cn/api/finduser/' + newVal, (res) => {
            console.log(res); //{status: 0, message: '用户名可用!'}
        });
    }
}

4.2格式

方法格式

​ 缺点1:无法立即触发

​ 缺点2:侦听的是对象中属性的变化,则无法侦听到

对象格式

​ 好处1:通过 immediate 选项立即触发

​ 用Handler属性定义函数

watch: {
    username: {
        // handler 是固定写法,表示当 username 的值变化时,自动调用 handler 处理函数
        handler(newVal, oldVal) {
            console.log(newVal); console.log(oldVal);
        },
            // 表示页面初次渲染好之后,就立即触发当前的 watch 侦听器 | 默认值是false
            immediate: true
    }
}

​ 好处2:通过deep选项侦听到对象中属性的变化

<input type="text" v-model="info.username">
    
    data:{
        info: {
            username: "admin"
        }
    }

    watch: {
        /*
        侦听info的变化
        */
        // info(newVal) {
        //   console.log(newVal);
        // }
        info: {
            handler(newVal) {
                console.log(newVal);
            },
                deep: true
        }
    }

5.计算属性

computed节点–方法格式

      computed: {
        rgb() {
          // this表示当前的vm实例
          return `rgb(${this.R},${this.G},${this.B})`;
        }
      }

当做属性使用— {{ rgb }}

好处:实现代码的复用 ||只要计算属性中依赖的数据源发生变化,就会自动重新赋值

6.axios

作用:是一个专注于网络请求的库

发数据与请求数据,业务专一

// 调用axios得到的是一个promise对象
const resPromise = axios({
    method: "",
    url: ""
});
// books.data 才是存放实际数据的地方
resPromise.then(function (books) {
    console.log(books.data);
});

6.1发起GET请求

const resPromise = axios({
    // 请求方式
    method: "GET",
    url: "",
    // URL中的查询参数
    params: {
        id: 1
    },
    // 请求体参数
    data: {}
}).then(function (res) {
    console.log(res);
});

6.2发起POST请求

axios({
    // 请求方式
    method: "POST",
    url: "",
    // 请求体参数
    data: {
        name: "zd",
        age: 19
    }
}).then(function (res) {
    console.log(res);
});

6.3 await async

只要返回的是一个promise实例,就可以在前面加 await ,加上之后返回的是数组

同时,await只能用在被async修饰的方法中

eg:

$(this).addEventListener('click', async function () {
    const result = await axios({
        xxx
    }.then(function (res) {

    }));
});
  • 注意 这里的result不是服务器返回的真实数据

6.4 仅选择data属性

——返回真正的数据

const { data } = await axios(xxxx)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AHuXViV6-1668928664011)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20220902200345821.png)]

<script>
    $(".get").on('click', async function () {
    /*
      await axios()是一个对象,里面有6个属性,{ data }只取其中的data
       { data:res } 将解构出来的数据重命名
      */
    const { data: res } = await axios({
        method: "GET",
        url: "http://www.liulongbin.top:3006/api/getbooks"
    });
    console.log(data);
});
</script>

6.5axios.get() axios.post()

$(".get").on('click', async function () {
	axios.get("url地址", {
		// get参数
		params: {}
	});
});

7.vue-cli

7.1单页面应用程序

单页面应用程序(英文名:Single Page Application)简称 SPA,顾名思义,指的是一个 Web 网站中只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成

7.2vue 项目

main.js

// 创建vue实例对象
new Vue({
  // 把render指定的组件渲染到HTML页面中
  // 拿到test.vue里面的html部分
  // 拿vue将html的#app部分替换为test
  render: h => h(Test),
}).$mount('#app')

7.3创建运行

vue create xxxx

  • Matually
  • Babel & CSS
  • 选择2.x版本
  • 选择less – css处理器
  • 放独立文件 In dedicated config files
  • yes
  • tietiepreset
  • 装包时鼠标不可动,否则需要轻触窗口ctrl+C

npm run serve — 开发时运行

7.4app.vue根组件

el–所控制的区域就是根

App渲染变成了根组件

render: h => h(App), — render 函数中渲染的是哪个 .vue组件,哪个就是根组件

8.VUE组件

8.1构成

 template -> 组件的模板结构
 script -> 组件的 JavaScript 行为 

 style -> 组件的样式
// 组件的结构 只能有一个根结点
<template>
  <div>
    
  </div>
</template>
// 组件行为
<script>
// 对外导出
export default {
  data() {
    return { username: "zs" };
  },
  methods: {

  },
  watch: {},
  filters: {},
};
</script>  
// 组件样式 默认是普通样式
<style lang="less">
    	...
</style>

8.2练 习 demo-second

  • 所有的组件都在components文件夹中

8.3使用组件的3步骤

<script>
// 1.导入需要的组件
import left from "@/components/left.vue";
import right from "@/components/right.vue";

// 2.使用components节点注册属性
export default {
  components: {
    left,
  },
};
</script>

<!-- 3.注册好的组件,以标签的形式使用 -->
 <left></left>

【注意】components注册的是私有属性

【补充】配置路径的插件:Path Autocomplete

setting.json中添加:

// 导入文件时是否携带文件的扩展名
"path-autocomplete.extensionOnImport": true,
// 配置@的路径提示
"path-autocomplete.pathMappings": {
    "@": "${folder}/src"
},

要在该文件夹打开vscode才生效

8.4注册全局组件

main.js — Vue.component()方法

import Vue from 'vue'
import count from "@/components/count.vue";
// 2.2 全局注册
Vue.component("MyCount", count);

auto close tag 插件 自动配置

8.5组建中的props (default type required)

提供多用户使用,增大复用性;

允许使用者在封装属性的时候可以自定义一些属性去用

count.vue — 组件的封装者

// props自定义组件  允许使用者自定义当前组件初始值
// props中的数据可以直接在模板结构中被使用
// 注意 props是只读属性 不可改变,通过this.init获取
props: ["init"],
    
//props的值转存到data中  
data() {
    return {
        count: this.init,
    };
},

right/left.vue — 组件的使用者

<!-- init="9"是字符串   :init="9"是数字 -->
<MyCount init="9"></MyCount>

props不写数组,改成对象,则可以赋初始默认值;数组的默认值是undefined

// 自定义属性
props: {
    init: {
        // 如果外界使用count时没有传递init属性的值,则默认为0
        default: 0,
      	type: Number,
      	required: true,
    },
},

8.6 scoped

left.vue中:

// 添加scoped 限定h3属性只能在left.vue生效,而不会全局都生效  
<style lang="less" scoped>
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
h1{
	color:pink;
}

使得属性为 h1[v-data-001] 的样式改变

8.7 /deep/

left.vue中

// 添加scoped 限定h3属性只能在left.vue生效,而不会全局都生效  
/deep/h5{
	color:pink;
}

使得属性为 [v-data-001] h5 — [v-data-001]的后代选择器中的h5 的样式改变

应用场景

父组件中直接改造子组件的样式

如果用到第三方组件库的时候,如果需要改变第三方组件默认样式,则需要用到 /deep/

9.组件的生命周期

创建一个组件的实例—就是以标签的形式去使用的过程

生命周期(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。

生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。

  • 创建阶段:new Vue() – beforeCreate – created – beforeMount – mounted
  • 运行阶段:beforeUpdate – updated
  • 销毁阶段:beforeDestroy – destroyed

注意:生命周期强调的是时间段,生命周期函数强调的是时间点。

https://v2.cn.vuejs.org/v2/guide/instance.html#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%9B%BE%E7%A4%BA

//beforeCreate 阶段,props|data|methods都尚未创建,处于不可用状态
  // 创建阶段的第一个生命周期函数
  beforeCreate() {
    console.log(this.info); //undefined
    console.log(this.username); //undefined
    console.log(this.show); //undefined
  },
    
      
//----------------------------------------------------------------------
      
      
// 可以使用 props data methods
// 但组件的模板结构尚未完成,template里面的内容还没有被渲染,不能操作dom结构
  created() {
    console.log(this.initdata); //Hello
    console.log(this.username); //zs
    this.show(); //调用了test组件的show方法
  },     
// created函数很常用,常用于调用methods中的方法请求服务器的数据,
//并将数据转存到data中供template模板使用
      initgetBooks() {
          const xhr = new XMLHttpRequest();
          // 监听的load事件
          xhr.addEventListener("load", () => {
              // res对象格式,res.data是数组数据
              const res = JSON.parse(xhr.responseText);
              this.books = res.data;
              console.log(this.books);
          });
          xhr.open("GET", "http://www.liulongbin.top:3006/api/getbooks");
          xhr.send();
      },
// 可以发起ajax请求去请求数据,通过methods请求好之后存进data中,便于渲染
  created() {
    this.initgetBooks();
  },   
// 不能操作dom结构          
	const domH3 = document.querySelector("#Myh3");
	console.log(domH3); //null 还没有被创建

      
//----------------------------------------------------------------------
      
/**
 * 将要渲染
 * 也是拿不到DOM结构
 */
  beforeMount() {
    console.log("beforeMount");
    const domH3 = document.querySelector("#Myh3");
    console.log(domH3); //null 还没有被创建
  },
       
//----------------------------------------------------------------------
       
   /**
   * 完成渲染DOM结构
   * 最早只能在当前的mounted阶段执行DOM
   */
  mounted() {
    console.log("mounted");
    console.log(this.$el); //得到当前的HTML结构
  },     
      
 
//----------------------------------------------------------------------
            
  //最少运行0次(数据不变化时)
  // 数据变化时,为了能获取最新的dom结构,必须写在updated的生命周期函数内
  /**
   * beforeUpdate
   * 数据发生变化时开始运行(数据赋值、数据修改...)
   * 将要渲染
   */
  beforeUpdate() {
    console.log("beforeUpdate");
    console.log(this.msg);
    const domMsg = document.querySelector(".msg");
    console.log(domMsg); //null 还没有被创建
    console.log("将要渲染,即这里的数据改变,但dom结构的内容还没改变");
    console.log(domMsg.innerHTML);
  },
  /**
   * updated
   * 已经渲染好了
   */
  updated() {
    console.log("updated");
    const domMsg = document.querySelector(".msg");
    console.log(domMsg.innerHTML);
  },      
      
      
//----------------------------------------------------------------------
// v-if = "flag"  --  true/false决定是否销毁      

  /**
   * beforeDestroy
   * 将要销毁,但还未销毁
   */
  beforeDestroy() {
    // 点击了就开始触发
    console.log("beforeDestroy");
    console.log(this.msg); //
    const domMsg = document.querySelector(".msg");
    console.log(domMsg); //dom元素已销毁
  },
  /**
   * destroyed
   */
  destroyed() {
    // 点击了就开始触发
    console.log("destroyed");
    console.log(this.msg); //
    const domMsg = document.querySelector(".msg");
    console.log(domMsg); //dom元素已销毁
  },      

10.组件之间的数据共享

10.1 父子组件之间的数据共享 父–>子

① 子组件新建props属性

注意:props的属性是只读的,不建议改动

<template>
  <div id="container">
    <p>这是msg内容:{{ msg }}</p>
    <p>这是info内容:{{ info }}</p>
  </div>
</template>

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

② 父组件内

导入:import Son from "@/components/Son.vue";

注册:components: { Son, },

标签化使用:<Son v-bind:msg="message" v-bind:info="userInfo"></Son>

message,userInfo是父组件的data节点的属性

③ 注意点

10.2 父子组件之间的数据共享 子–>父

使用自定义事件 $emit()方法

① 子属性先data节点定义数值

data() {
return {
count: 0,
};
},

② 父组件的data节点新建一个数据项接收子组件传来的值

data() { return { saveCountFromSonbro: 0, };},

③ 子组件的click事件函数内部新增一个自定义事件

  methods: {
    add() {
      this.count += 1;
      this.$emit("numchange", this.count);
    },
  },

④ 父组件注册的子组件实例中添加自定义事件

<SonBro @numchange="getCountFromSonbro"></SonBro>
<h3>自定义事件从子组件获取的值是:{{ saveCountFromSonbro }}</h3>
//-----------------函数中赋值------------------
  methods: {
    getCountFromSonbro(val) {
      this.saveCountFromSonbro = val;
    },
  },

10.3 兄弟组件之间的数据共享 vue2.x–EventBus

Son.vue(发送方) 将数据传给 SonBro.vue(接收方)

① 设置发送方的数据值 与 接收方的数据空值

② EventBus.js 里面导入Vue,new一个Vue实例对象,exports default实例对象 ==> 谁导入的EventBus.js文件谁就可以使用Vue实例对象

import Vue from ‘vue’

export default new Vue();

③ 发送方添加一个函数,调用 bus.$emit(‘事件名’, 发送的值) 发送数据

<button @click="sendData">发送数据</button>

  data() {
    return {
      message: "兄弟组件发送方要准备发送的消息",
    };
  },
  methods: {
    sendData() {
      bus.$emit("share", this.message);
    },
  },

④ 接收方,调用 bus.$on(‘事件名’, (:就是接收的参数值)val=>{})

bus.$on(‘事件名’, val=>{})

数据发生变化,写在created节点内

<h5>接收的数据是:{{ msgFromBrother }}</h5>

  data() {
    return {
      count: 0,
      // 接收方 先设置为空
      msgFromBrother: "",
    };
  },
  // 接受数据 发送数据 数据变换的时候
  created() {
    bus.$on("share", (val) => {
      this.msgFromBrother = val;
    });
  },

11.ref的引用

jQuery 操作DOM

Vue中,MVVM优势,仅需维护数据即可,数据操纵视图,极少需要操作DOM的方案–ref

每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。

this.$ref = {}

方便取到某个DOM元素

11.1 引用DOM

步骤 【注意,一个是 ref,一个是 $refs】

① 给模板的html元素添加ref <h1 ref="myH1">vue 组件</h1>

② 拿到对应的DOM结构 this.$refs.myH1.style.color = "green"

11.2 引用组件

① 在被引用组件的标签上添加ref

ref = “自定义名字”

<Son v-bind:msg="message" v-bind:info="userInfo" ref="change"></Son>

② 在组件内添加函数

this.$refs.change.info.name = "针对对象修改的info(新建一个对象)";

组件实例 this . $refs . 前面的ref自定义名字 . 开始引用被引用组件的内容

11.3 this.$nextTick( ()=>{} )

应用常见:一些需要延迟执行的代码,比如等待DOM渲染完毕后

    showInput() {
      this.flag = !this.flag;
      //获取焦点
      /**
       *  this.$refs.iptRef.focus();
       *  Cannot read properties of undefined (reading 'focus')
       *  解读:this.$refs.iptRef是undefined的
       *  原因:bool值是变成true,但页面是还没渲染beforeUpdate,所以文本框还没有
       *  解决:Vue提供的方法:this.$nextTick(callback)---把callBack里面的方法延迟执行,数据重新渲染好再执行
       */
      this.$nextTick(() => {
        this.$refs.iptRef.focus();
      });
    },

13.购物车案例

① 获取数据

APP中导入axios const axios from "./lib/axios.js"

methods中,定义initCartList函数,通过 const {data:res} = await axios.get('url') 请求数据

created 生命周期函数中,调用 this.initCartList() 方法,输出数据变化后得到的数据

② 存储数据到data里面,为了渲染在页面上

先写好Goods组件模板

HTML结构
数据
	使用自定义属性props:{aaa:{default:"", type:Number/Boolean/String, required:true/false}}
	绑定属性 v-bind/:src="aaa"

Vue组件内利用循环渲染商品,并绑定Goods的自定义属性,给其赋值

<Goods
 <!-- Goods子组件那边动态绑定是:{{title}}、{{price}}、:src="pic"-->          
      v-for="item in CartList"
      :key="item.id"
      :title="item.goods_name"
      :price="item.goods_price"
      :pic="item.goods_img"
      :state="item.goods_state"
      :id="item.id"
      :for="item.id"
></Goods>

③ 用户在App.vue点击state切换时,数据并不会同步更改,因为没有设置v-model;

另外:state在Goods.vue子组件中是在props节点中的,不可更改

所以:改用自定义事件的方式从子组件传输state状态给父组件的方式

  • 监听子组件的复选框状态的变化
    • @change = “” 事件
    • const newState = e.target.checked;
  • 将变化后的值&变化项的id传给父组件
    • 使用some方法.some(item => {})· 在数组 this.CartList 找到对应的id
    • 再根据找的的item项修改对应的state值
    //自定义事件 子->父
    // e = {id, value}
    stateToogle(e) {
      // 在数组里面找到对应的id的那一项 --- .some()方法
      this.CartList.some((item) => {
        // 找到了对应的一项  ---> 修改
        if (item.id === e.id) {
          item.goods_state = e.value;
          return true; //终止循环
        }
      });
    },

④ 全选状态的修改

  • 点击【全选】按钮方式

    • Footer.vue 增加自定义事件,传送当前按钮的状态

    • App.vue 绑定事件,every/some方法改变全部复选框的状态

    • App.vue
          getNewStage(e) {
            // 在数组里面找到对应的id的那一项 --- .some()方法
            this.CartList.some((item) => {
              // 找到了对应的一项  ---> 修改
              if (item.id === e.id) {
                item.goods_state = e.value;
                return true; //终止循环
              }
            });
          },
      <Footer @full-change="getFullStage" :isFull="fullState"></Footer>
      __________________________________________________________________________________
      Footer.vue
        methods: {
          // 监听全选复选框的变化 作为参数传给自定义事件 到父组件
          fullChange(e) {
            this.$emit("full-change", e.target.checked);
          },
        },
      
  • 点击【普通复选框】方式

    • 判断是否所有复选框都选中 ---- 计算属性 ---- every方法

    • 属性绑定

        computed: {
          // 计算属性 判断有无全选
          fullState() {
            return this.CartList.every((item) => item.goods_state);
          },
        },
            
        <Footer @full-change="fn" :isFull="fullState"></Footer>    
      

⑤ 计算商品总价

  • Counter.vue:自定义属性 num

  • Goods.vue:

    • 导入Counter,v-bind绑定属性num :num=“count”
  • App.vue:

    • 导入Goods,v-bind绑定属性count :num=“item.goods_count”

    • 计算属性 computed

    •     amt() {
            // 过滤 -- 筛选出被勾选的,找到符合true的
            return this.CartList
            	.filter((item) => item.goods_state)
            	.reduce(
              (amt, item) => (amt += item.goods_price * item.goods_count;),0
            	);
          },
      Footer标签内添加 --- :amount="amt"    
      

⑥ 加购数量改变

多层嵌套下也可以用EventBus(App–Goods–Counter)

goods

methods: {
    sub() {
      let nums = this.num - 1 < 0 ? 0 : this.num - 1;
      const obj = { id: this.id, value: nums };
      bus.$emit("share", obj);
    },
    add() {
      const obj = { id: this.id, value: this.num + 1 };
      bus.$emit("share", obj);
    },
  },

app

  created() {
    console.log(this.initCartList());

    bus.$on("share", (val) => {
      this.CartList.some((item) => {
        if (item.id === val.id) {
          item.goods_count = val.value;
        }
      });
    });
  },

14.动态组件

  • 概念
    • 动态组件指的是动态切换组件的显示与隐藏
    • 不像v-if v-else 那么繁琐

14.1使用

  • vue 提供了一个内置的 组件,专门用来实现动态组件的渲染。
  • component在中类似一个占位的组件,添加 is = “components内注册的组件”
    • <component is="Left"></component>
  • 动态绑定
    • data节点定义名字 isName: "Left",
    • 再动态绑定 :is=“” <component :is="isName"></component>
  • 应用
    • 类似页面上 【我的】【首页】此类功能的切换

14.2 keep-alive

keep-alive 保持状态、被缓存 — 防止组件切换隐藏期间被销毁destroy

  • 使用(保证使用同一个实例,不会频繁created&destroyed)

    • <keep-alive>
      	<component :is="isName"></component>
      </keep-alive> 		    
      

14.3 deactivated & activated

  • 当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
  • 当组件被激活时,会自动触发组件的 activated 生命周期函数。
  • 第一次创建时 同时执行 created & actived

14.4 指定要缓存的组件 keep-alive 的 include 属性

  • <keep-alive include="Left, xxx, aaa">
            <component :is="isName"></component>
    </keep-alive>
    
    补充:exclude 指定谁不缓存
    注意:exclude & include只能使用一个
    首页展示固定数据的组件
    
  • 可以缓存多个,逗号隔开

14.5 了解-修改组件名称

  • 默认为注册时的名称 components:{Left}

  • 修改:Left.vue中添加节点name

15.插槽 Slot

15.1基础

  • 概念

    • 由组件的使用者去定义这一模块
  • 使用

  app.vue 
    <Left>
    	<p>这是在app.vue内添加的left区域的内容s</p>
    </Left>
    left.vue
    添加 <!-- 
    		声明一个插槽区域,名称默认为default
    	-->
        <slot></slot>
  • 插槽名称的使用

    • 注意点

      • v-slot 要在template标签内
      • template标签只起到包裹作用
<slot name="xxx"></slot>
<template v-slot:xxx>
	<p>
		加入插槽的内容
	</p>
</template>
  • 插槽简写形式 v-slot: ==== #

  • 后备内容

    • 用户没有指定的时候声明
    • 默认内容

15.2高级

  • 具名插槽
<template #title>
      <p>MyLife</p>
</template>
<slot name="title"></slot>  
  • 作用域插槽(提供属性给父组件使用)

    • 封装组件时为预留的slot提供属性对应的值msg,称作:作用于插槽

    • 取到的值是一个对象,可以进行解构赋值 {msg, user, xxx} – 只取对象中的msg\user\xxx... — 可以弄多个

   <template #content="scope">
        <p>Life is so damn</p>
        <p>{{ scope.msg }}</p>   //hello 这是作用域插槽
    </template>

    <slot name="content" msg="hello 这是作用域插槽"></slot>
  • <slot name="content" msg="hello 这是作用域插槽" :user="userinfo"></slot>
    
    _____________________________________________________________________________
    
          <template #content="{ msg, user }">
            <p>Life is so damn</p>
            <p>{{ msg }}</p>
            <p>{{ user.name }}</p>
          </template>
    

15.3插槽应用 — 购物车案例重构

描述:购物车案例的数量counter组件在变化的时候,需要先传给Goods,再传给APP;故使用EventBus直接Counter传给App

改进为:在Goods处放一个插槽 — 此时在Goods放的内容都会被填充到Counter组件中

counter 也可以读取 item项

    <Goods>
      <Counter
        :num="item.goods_count"
        :id="item.id"
        :num-change="shareNum(item)"
      ></Counter>
    </Goods>

counter数值的变化 (子 —> 父)

自定义事件

传数据,函数名(参数),参数是item.id,这里不用再在counter那边定义;

参数用了之后,要想得到自定义事件传来的值,剧需要再加上一个$event

@num-change="getNum(item, $event)"

getNum(item, e) {
	item.goods_count = e;
},

这里不需要再接收id的值找到对应的项,本身就可以拿到item了

16.自定义指令

私有自定义指令

16.1 bind绑定(只能绑定1次)

节点 directives

直接定义绑定 bind(el) {}

<h1 v-color>App 根组件</h1>  // 自定义节点
  // el固定写法,表示所绑定的DOM元素
  directives: {
    color: {
      bind(el) {
        el.style.color = "green";
      },
    },
    
 <h1 v-color>App 根组件</h1>   

增加数据节点绑定 bind(el, bingding) {}

v-color=“color” 传入的是一个变量

data节点添加
color: "purple",

    
<h1 v-color="color">App 根组件</h1>
  directives: {
    color: {
      bind(el, binding) {
        el.style.color = binding.value; 
      },
    },
  },

直接传变量的值

<p v-color="'yellow'">直接传变量值绑定</p>

16.2 update绑定

可以满足DOM元素在更新的时候再次绑定

16.3 bind&update函数简写

当bind update的业务逻辑一样时可写

    color(el, binding) {
      el.style.color = binding.value;
    },

全局自定义指令

main.js

// 全局定义自定义组件
/**
 * Vue.directive("定义好指令的名称", 处理函数方式);
 *  Vue.directive("xxx", {
 *      bind(){},
 *      update(){}
 *  }});
 */
Vue.directive('color', function (el, binding) {
  el.style.color = binding.value;
});

17.eslint

17.1新建

vue create — Manually… — 勾选加上css(Linter就是默认安装aslint)— 2.x — less — 选择Eslint对应的语法规范:Standard config标准配置 — Lint on save保存时解决 — 不存预设reset–n

.eslintrc.js

'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
//开发阶段不执行,发布阶段执行

debugger

写哪停哪,在网页f12source页面

https://eslint.bootcss.com/docs/rules/ 官网可添加修改自定义规则

17.2 下载配置插件

ESLint

setting.json添加:

  // ESLint 插件的配置
  "editor.codeActionsOnSave": {
    "source.fixAll": true,
    "editor.defaultFormatter": "octref.vetur"
  },

新建.prettierrc文件在用户目录下

18.axios基本用法

18.1 npm安装axios

18.2 选择性写

组件命名报错 “Component name “XXX“ should always be multi-word”的解决方法:.eslintrc.js
	// 关闭组件命名规则
    'vue/multi-word-component-names': 'off'

18.3 问题

Cannot find module 'babel-eslint' 
	————  npm install  eslint babel-eslint -g

18.4 axios的基本使用方式

导入:import axios from 'axios'

GET请求:请求路径 
const { data: res } = await axios.get('http://www.liulongbin.top:3006/api/get')

POST请求:请求路径 & 发送的数据
    async postInfo() {
      const { data: res } = 
      	await axios.post('http://www.liulongbin.top:3006/api/post', 
      					{ name: 'zs', age: 22 }
      					)
    },

18.5 优化

理解,每一个.vue都是一个vue的实例,先axios全局化

在main.js:先axios全局化,通过Vue构造函数的构造属性【 h t t p 只 是 一 个 命 名 】 ‘ V u e . p r o t o t y p e . http只是一个命名】 `Vue.prototype. httpVue.prototype.http= axios` 挂载

将axios挂载到vue的原型上,让axios变成一个共享成员,这样每个组件就可以访问我们的axios了,不再需要额外import导入axios。

今后要发请求,组件中不需要再导入,而是可以直接 this.$http.get(....)

方便操作:全局配置 axios 的请求根路径 axios.defaults.baseURL = 'http://www.liulongbin.top:3006'

18.6 缺点

无法实现API接口的复用

即:多个组件都要请求数据的话,都要进行 this.$http.get操作

为了方便我们api接口的复用,可以改成写一份代码,适用三个组件,而不是写三个冗余代码。

正因为这种复用api的需求,所以可以把组件中使用组合式API创建的逻辑抽取出来封装成可复用的模块

19.前端路由的概念与原理

19.1 SPA 与前端路由

  • SPA:单页面应用程序

  • SPA 指的是一个 web 网站只有唯一的一个 HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。此时,不同组件之间的切换需要通过前端路由来实现。

  • 结论:在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成!

19.2 前端路由

  • Hash 地址与组件components之间的对应关系。

    • component动态组件标签
  • Hash 地址 — 之前的锚链接#

    • 页面不会刷新,但会有浏览历史

    • #/xxx ---- 都是hash地址 — location.hash可以输出其值

    • <div class="side-bar">
          <a href="#b1">b1</a>
          <a href="#b2">b2</a>
          <a href="#b3">b3</a>
          <a href="#b4">b4</a>
      </div>
      
      <div class="box" id="b1"></div>
      <div class="box" id="b2"></div>
      <div class="box" id="b3"></div>
      <div class="box" id="b4"></div>
      
  • 前端路由的工作方式

  • 过程:点击了前端页面上的路由链接 — 导致了URL地址栏中的hash地址的变化 — 前端路由监听hash地址的变化 — 渲染hash地址对应的组件

  • 应用

    • onhashchange 事件(了解即可,vue-router会自动去创建)

    • created() {
        // 只要当前的 App 组件一被创建,就立即监听 window 对象的 onhashchange 事件
        window.onhashchange = () => {
          console.log('监听到了 hash 地址的变化', location.hash)
          switch (location.hash) {
            case '#/home':
              this.comName = 'Home'
              break
            case '#/movie':
              this.comName = 'Movie'
              break
            case '#/about':
              this.comName = 'About'
              break
          }
        }
      },
      

20.vue-router 的基本使用

vue-router 的官方文档地址:https://router.vuejs.org/zh/

20.1 安装与配置

① 安装 vue-router 包 — npm i vue-router@3.5.2 -S
② 创建路由模块 — 新建 router/index.js 路由模块

  • 导包 import xxx from ‘xxxxx’ — Vue & RouterVue

  • 将路由装为Vue下的插件—使用 Vue.use(VueRouter)

  • 创建实例对象 new VueRouter()

  • 对外共享实例对象 export default router

③ 导入并挂载路由模块 (app2.vue 写链接+占位符 — 路由模块写对应关系)

  • 在main.js的Vue实例中配置

  • 导入index.js中的router模块 import router from '@/router' 只有一个index.js文件在里面,可以只写外面的文件夹

  • new Vue({
      render: h => h(App),
      // 在 Vue 项目中,要想把路由用起来,必须把路由实例对象,通过下面的方式进行挂载
      // router: 路由的实例对象
      router
    }).$mount('#app')
    

④ 声明路由链接和占位符

  • <!-- 当安装和配置了 vue-router 后,就可以使用 router-view 这个组件了 -->
    <!-- 作用:占位符,展示变化完成后的组件 || vue-router提供-->
    
     <router-view></router-view>
    
  • 在路由模块【router/index.js】中声明对应关系

    • 在router实例对象中添加一个 routes:[{path:'#后面的值', component:要展示的组件|先导入}, {}, {}] 数组,用于定义【hash地址】与【组件】之间的关系

    • 【注意】是 routes 不是 routers

    • const router = new VueRouter({
        routes: [
          { path: '/home', component: Home },
          { path: '/movie', component: Movie },
          { path: '/about', component: About }
        ]
      })
      

20.2 router-link

  • router-link 代替 a 链接

    •     <!-- 当安装和配置了 vue-router 后,就可以使用 router-link 来替代普通的 a 链接了,好处:不用写 # -->
          <!-- <a href='#/home'>首页</a> -->
          <router-link to="/home">首页</router-link>
      

20.3 路由重定向 redirect

  • 用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。

    通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:

  • { path: '/', redirect: '/home' },
    

20.4 嵌套路由

  • 通过路由实现组件的嵌套展示,叫做嵌套路由。

  • 在这里插入图片描述

  • 在路由模块内声明 父级路由规则 & 子级路由规则children:[]

    • const router = new VueRouter({
        routes:[
          {path:'xxx 跟 router-link 的 to内容一样', component:导入的组件},
          {path:'', component:xxx},
          {path:'/about', component:xxx, children:[{path:'tab1',component:Tab1},{},{}]}
        ]
      })
      // 注意:子路由规则的path不需要再加上 ‘/’了
      
    • 设置tab1是about的默认页面

    • {path:'tab1',component:Tab1} 改成 {path:'',component:Tab1}<router-link to="/about/tab1">Tab1</router-link> 改成 <router-link to="/about">Tab1</router-link>
      

20.5 动态路由匹配

  • 把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。

    在 vue-router 中使用英文的冒号(:)来定义路由的参数项。

    •     <router-link to="/movie/1">A电影</router-link>
          <router-link to="/movie/2">B电影</router-link>
          <router-link to="/movie/3">C电影</router-link>
      
    • { path: '/movie/:id', component: Movie }  //:id动态匹配
      /*
       to="/movie/1" movie/??自定义
       :? 自定义
      */
      
  • 对应关系设置好后,在movie组件(Movie.vue)中,希望拿到对应的id的值(动态参数值)

    • 【方式1 $this实例对象中】

    • this 对应的实例
      this.$route.params.id	//id----自定义名称 || this选择省略 $route.params.id
      
    •     <!-- this.$route 是路由的“参数对象” -->
          <!-- this.$router 是路由的“导航对象”,提供了一系列的API方法 -->
      
    • 【方式2 props:true传参】

    • { path: '/movie/:id', component: Movie, props: true } //前端路由
      
    • props: ['id'], //接收数据
      
    • <!-- 使用数据 -->
      <h3>Movie 组件 ---- 打印对应的id {{id}}</h3>
      
  • 扩展:路径参数 & 查询参数

    • router-link to=“/movie/B?name=ls&age=22” ---- 路径参数是:B || 查询参数是:name=ls&age=22

20.6 vue-router 中的编程式导航 API

  • ① this.$router.push(‘hash 地址’)

    ⚫ 跳转到指定 hash 地址,并增加一条历史记录

    eg:使用按钮而不是链接的方式跳转页面

    ② this.$router.replace(‘hash 地址’)

    ⚫ 跳转到指定的 hash 地址,并替换掉当前的历史记录

    ③ this.$router.go(数值 n)

    ⚫ 实现导航历史前进、后退

    ⚫ 常用于前进/后退一层:(没有this,直接写在行内)

    ​ ① $router.back()

    ​ ⚫ 在历史记录中,后退到上一个页面

    ​ ② $router.forward()

    ​ ⚫ 在历史记录中,前进到下一个页面

20.7 了解导航守卫 (全局前置守卫)

  • 避免在未登录的情况下访问到首页

  • 全局前置守卫

    每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行

    访问权限的控制:

    // 为router实例对象声明前置全局导航,参数是回调函数守卫方法
    // 触发时机:只要出发了导航跳转,必然会触发fn回调
    // fn()接收的形参:to || from || next [from A to C,next允许放行]
    router.beforeEach(function(to, from, next) {
      // to 将要访问的路由的信息
      console.log(to)
      // from 将要离开的路由的信息
      console.log(from)
      // next函数 --- 表示放行
      next() //都可以放行
    })
    
  • next 函数的 3 种调用方式最终导致的结果

    当前用户拥有后台主页的访问权限,直接放行:next()

    当前用户没有后台主页的访问权限,强制其跳转到登录页面:next(‘/login’)

    当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)

      if (to.path === '/main') {
        // 判断后台主页是否有token
        const token = localStorage.getItem('token')
        if (token) {
          //有token 继续放行
          next()
        } else {
          // 无token 跳转登录
          next('/login')
        }
      } else {
        // 不去后台 都可以放行
        next()
      }
    

21.vue-router 的常见用法

22.后台管理案例

  1. router/index.js 配置 vue-router,在main.js导入路由模块

  2. 渲染登录页面 components/MyLogin.vue ,在路由模块加入对应的组件以及routes:[{}]对应关系

  3. 登录界面–双向绑定数据username&password,.trim去除左右多余的空格

  4. 点击登录按钮,根据输入值是否正确,判断是否进入主页面

  5. login() {
          if (this.username === 'admin' && this.password === '000000') {
            localStorage.setItem('token', 'Bearer xxxxx')
            this.$router.push('/home')
          } else {
            localStorage.removeItem('token')
            this.reset()
          }
        }
    
  6. 路由模块绑定首页对应关系

  7. 首页的头部区域,左侧区域也是直接加组件,路由模块绑定,右侧区域

  8. 定义子级路由规则、

  9. 用户表格的渲染 – tr里面进行for循环 –

  10. 点击用户详情的动态跳转

  11. 传参跳转 @click.prevent="gotoDetail(item.id)"
    this.$router.push('/home/userinfo/' + id)  --- MyUser.vue
    
    // 用户详情的路由
    { path: 'userinfo/:id', component: UserInfo, props: true } --- index.js
    
  12. 退出登录 — 清除token,跳转页面

  13. 后退 — @click="$router.back()"直接返回上一个链接

拓展

多条路径需要判断权限的情况

① if(to.path === 'xxx '||to.path === ‘bbb’||to.path === ‘ccc’… )

② const arr = [‘xxx’, ‘bbb’, ‘ccc’]

​ 判断 to.path 是否是数组的其中之一

​ (pathArr.indexOf(to.path) != 1) ————证明在数组里面

③ 将数组arr单独抽取为js文件

​ export default [‘/home’, ‘/home/users’, ‘/home/rights’]

23.头条项目

在这里插入图片描述

history??? —no

components & views 文件夹的区别

​ 某个组件通过路由来进行动态切换 — views

​ 某个组件是可复用的 — components

23.1 安装与配置 Vant 组件库 — 移动端

官网:https://vant-ui.github.io/vant/#/zh-CN

  1. 快速入手:npm按照、第三种按照组件的方式(开发阶段)

  2. import Vue from 'vue'
    import Vant from 'vant'
    import 'vant/lib/index.css'
    
    Vue.use(Vant)
    //main.js
    
  3. 测试使用:接下来类似button处的引入就不用再继续了,(有了第一步)可以直接使用

  4. 主界面 & 用户界面 都是通过路由加载的,放在views文件夹中【规范-组件名大写】

  5. 结构分析:上-router-vue占位符;下-tabbar组件

  6. 官网tabbar标签栏–得到结构&选择路由模式(开启对应&切换);icon图标–选择需要的图标

  7. 注意:组件命名解决驼峰命名法:‘vue/multi-word-component-names’: ‘off’,

  8. 使用路由规则:index.js路由模块内–导入组件||主页&个人中心路由规则{path,component}

  9. 顶部nav bar,看官网,添加固定顶部属性 fixed

  10. 考虑以为NavBar & TabBar的遮挡,所以home-container要设置padding

  11. <van-nav-bar title="标题" fixed />

  12. 部分修改不了的第三方默认样式,可以在样式类名前加上 /deep/ .xxx{}

  13. axios请求数据:npm装包

  14. 在Vue项目配置axios:main.js----Vue.prototype.$http=axios----复用性差----改进:src/utils/request.js封装axios的实例,直接调用即可

    // import Vue from 'vue'
    import axios from 'axios'
    
    // const request = Vue.prototype.$http
    
    const request = axios.create({
      baseURL: 'http://www.liulongbin.top:8000'
    })
    export default request
    
  15. Home组件请求文章列表数据:导入request.js,使用request.get(‘url’,{ params:{对象参数} })、

      created() {
        this.initArticleList()
      },
      methods: {
        async initArticleList() {
          const { data: res } = await request.get('/articles', {
            params: {
              _page: this.page,
              _limit: this.limit
            }
          })
          console.log(res)
        }
      }
    

    Failed to resolve loader: less-loader ---- 解决:

    cnpm install less less-loader --save-dev

  16. 代码的复用 – 数promise 类型的代码 — Promise的实例对象

    // api/articlaAPI.js
    import request
    export const xxxxx = function(参数a, 参数b){ // 对外共享函数
        // return [new Promise()]
        return request.get('url', {
            params:{
                参数a, 参数b
            }
        })
    }
    
    //User.vue
    import { xxxxx } from '...articleAPI.js'
    methods:{
        async fn(){
            const{data:res} = await xxxxx(this.a, this.b)
            // xxxxx(this.a, this.b)返回Promise类型
        }
    },
    created(){this.fn()}    
    
  17. 将得到的res数组转存到data(新建一个空数组)里面 this.articlelist = res

  18. 文章为一个个模块,不需要路由跳转,直接放components内,根据数组循环将组件渲染出来

    <ArticleInfo v-for="item in articlelist" :key="item.id"></ArticleInfo>
    
  19. 文章组件使用传值:props:{title:{type:xxx,default:‘’}}、{{ title }}、:title = “item.title”

    通过数组定义多个可能的类型 type: [String, Number],

    自定义属性名如果是小驼峰形式comCount,则大写字母在绑定时要改成连字符::com-count=“item.comm_count”

    属性为对象格式

        cover: {
          type: Object,
          default: function () {
            return {
              type: 0
            }
          }
    

    ​ 默认值需要是function,返回一个默认值

    ​ 在Home.vue绑定一整个cover属性

    ​ 在ArticleInfo.vue中按需使用

    ​ 判断图片出现几张—v-if=“cover.type===1” —注意:是平级的,不能使用v-if/v-else

          <template #title>
            <div class="title-box">
              <!-- 标题 -->
              <span>{{title}}</span>
              <!-- 单张图片 -->
              <img :src="cover.images[0]" alt="" class="thumb" v-if="cover.type===1">
            </div>
            <!-- 三张图片 -->
            <div class="thumb-box" v-if="cover.type === 3">
              <img :src="item" alt="" class="thumb" v-for="(item, i) in cover.images" :key="i" />
            </div>
          </template>
    

    ​ 使用图片 vover.images[0] || 循环 v-for=“(item , i)in cover.images” :key=“i” 【添加i只是为了:key,避免报错】

  20. 加载更多的效果:Vant展示组件-List列表:https://vant-ui.github.io/vant/#/zh-CN/list

  21. 使用:van-list包裹需要加载的组件

    <van-list
      v-model:loading="loading"
      :finished="finished"
      finished-text="没有更多了"
      @load="onLoad"
    >
      <要包裹的标签 />
    </van-list>
    
    /* 数据的加载请求
    loading --- 是否正在加载下一页:请求-true,此时不会反复触发load绑定的事件,数据请求完成变成false
    finished --- 是否加载完毕,完成后变成true,并提示文本finished-text
    */
    
    // 数据初始值
    <script>
        data(){
            return{            
                // 是否正在加载 设置为true,防止一进入页面就加载load
                loading: true,
                // 是否加载完毕
                finished: false
            }
        },
        // 数据加载完毕后(initArticleList完成后),再将loading设置为false,方便下一页面的加载    
    </script>    
    
  22. 数据onload

      methods: {
        async initArticleList() {
          const { data: res } = await getArticleListAPI(this.page, this.limit)
          /**
           * 将当前页面的数据|数组,与原来的数据|数组合并
           * 不可用arr1.push[arr2] --- > [1,2,3,[4,5,6]]
           * 仅合并为一个大数组的方法 ...事件扩展符,提取元素中的每一个并合并在一起
           * const Arr = [...arr1, ...arr2] ---->[1,2,3,4,5,6]
           */
          this.articlelist = [...this.articlelist, ...res]
          // 数据res已获取完毕,取消正在加载状态
          this.loading = false
          /**
           * 判断是否到了最后一页,即新得到的数据是否为空(length=0),停止加载
           */
          if (res.length === 0) {
            this.finished = true
          }
        },
        onLoad() {
          this.page++ // 页面+1
          this.initArticleList() // 调用获取当前页面的方法
        }
      }
    
  23. 下拉刷新(下拉是在头部拼接请求的数据)

     <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
          <van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
            <ArticleInfo v-for="item in articlelist" :key="item.id" :title="item.title" :author="item.aut_name" :com-count="item.comm_count" :pubdate="item.pubdate" :cover="item.cover"></ArticleInfo>
          </van-list>
    </van-pull-refresh>
    
    <!--  
    添加 isRefresh 布尔值判断是否下拉刷新,判断方式:加一个参数isRefresh
    下拉刷新采用 新数据在前 的数据拼接方式
    结束时采用disable属性禁止刷新
    
    -->
    
        onRefresh() {
          this.page++ // 页面+1
          this.initArticleList(true) // 调用获取当前页面的方法,参数true表示当前是下拉加载
        }
    /*
    梳理:
    vant官网获取下拉&上拉代码
    判断有无已进入页面就触发加载的,有则根据属性改为true/false
    开始加载,首先页面++,然后调用加载函数
    在加载函数内部,给函数传参,判断此时是哪一种情况的加载
    	上拉加载的,数组拼接方式是[旧的,新的],
    	下拉加载的,数组拼接方式是[新的,旧的],
    	然后得到数据后,刷新方式都要关掉
    最后,根据得来的res数据的长度判断数据是否为空,进而关掉其finished	
    */
    
  24. 定制主题

针对情况:组件1修改默认的样式的时候,组件2并不会同步修改

  1. main.js中将css样式改成less样式

    // import ‘vant/lib/index.css’

    import ‘vant/lib/index.less’

  2. 新建文件vue.config.js 根目录下

    // vue.config.js
    module.exports = {
      css: {
        loaderOptions: {
          less: {
            // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
            lessOptions: {
              modifyVars: {
                // 直接覆盖变量
                'text-color': '#111',
                'border-color': '#eee',
                // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
                hack: `true; @import "your-less-file-path.less";`,
              },
            },
          },
        },
      },
    };
    
  3. 官网查询样式变量

    --van-nav-bar-background-color
    vue.config.js内加:'nav-bar-background-color': '#0077ff',
    注释掉:// hack: `true; @import "your-less-file-path.less";`
    重启
    
    另一种:使用自定义的less文件覆盖,无需重启 hack: `true; @import "自定义命名.less";`,
        注意,这里使用的是绝对路径---【webpack在进行打包的时候,底层使用的是node,js,因此这里可以使用path.join(__dirname,'./xxx')const path = require('path')
    const themePath = path.join(__dirname, './src/theme.less')
    	hack: `true; @import ${themePath};` 
    
  4. 样式覆盖

    // 在 theme.less 文件中,覆盖 Vant 官方的 less 变量值
    @blue: #007bff;
    
    // 覆盖 Navbar 的 less 样式
    @nav-bar-background-color: @blue;
    @nav-bar-title-text-color: #fff;
    
  5. 项目打包

  6. 默认的 npm run build 打包生成的dist文件夹,只能发布在服务器上,通过http协议打开,双击的file协议打不开

  7. https://cli.vuejs.org/zh/config/#publicpath指定部署路径

  8. publicPath,这个值也可以被设置为空字符串 ('') 或是相对路径 ('./'),vue.config.js添加publicPath: './',

  9. 主页面点击离开之后不会销毁:

    1. 添加缓存keep-alive > Home;
    2. 接着路由模块index.js记录路由信息,标记top值;
    3. 在路由模块index.js添加页面滚动的行为:scrollBehavior(to, from, savedPosition){},直接将保存的位置return出去
    4. Home组件,页面被activated激活时添加页面滚动监听,页面被deactivated缓存时移除页面监听

24.阶段总结

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值