vue3+ts+element-plus(包含vuex、router) 随笔

#vue3与vue2区别以及vue3优点

  1. 默认进行懒观察(lazy observation)在 2.x 版本里,不管数据多大,都会在一开始就为其创建观察者。当数据很大时,这可能会在页面载入时造成明显的性能压力。3.x 版本,只会对「被用于渲染初始可见部分的数据」创建观察者,而且 3.x 的观察者更高效。
  2. 更精准的变更通知。2.x 版本中,使用 Vue.set 来给对象新增一个属性时,这个对象的所有 watcher 都会重新运行;3.x 版本中,只有依赖那个属性的 watcher 才会重新运行。
  3. 3.0 新加入了 TypeScript 以及 PWA 的支持
  4. 部分命令发生了变化:
    a.下载安装 npm install -g vue@cli
    b.删除了vue list
    c.创建项目 vue create
    d.启动项目 npm run serve
  5. 默认项目目录结构也发生了变化:
    移除了配置文件目录,config 和 build 文件夹
    移除了 static 文件夹,新增 public 文件夹,并且 index.html 移动到 public 中
    在 src 文件夹中新增了 views 文件夹,用于分类 视图组件 和 公共组件

移除data 取而代之使用 setup
在新版当中setup等效于之前2.0版本当中得到beforeCreate,和created,它是在组件初始化的时候执行,甚至是比created更早执行。值得注意的是,在3.0当中如果你要想使用setup里的数据,你需要将用到值return出来,返回出来的值在模板当中都是可以使用的。
假设如果你不return出来,而直接去使用的话浏览器是会提醒你:
如果要想在页面中使用生命周期函数的,根据以往2.0的操作是直接在页面中写入生命周期,而现在是需要去引用的,这就是为什么3.0能够将代码压缩到更低的原因。

如何知道数据发生了变化,Vue3 之前使用了 ES5 的一个 API Object.defineProperty。Vue3 中使用了 ES6 的 Proxy,都是对需要侦测的数据进行 变化侦测 ,添加 getter 和 setter ,这样就可以知道数据何时被读取和修改。
如何知道数据变化后哪里需要修改,Vue 对于每个数据都收集了与之相关的 依赖 ,这里的依赖其实就是一个对象,保存有该数据的旧值及数据变化后需要执行的函数。每个响应式的数据变化时会遍历通知其对应的每个依赖,依赖收到通知后会判断一下新旧值有没有发生变化,如果变化则执行回调函数响应数据变化(比如修改 dom)。


#vue3生命周期

vue3中使用的生命周期
使用defineComponent 构建应用及绑定事件
使用setup(props,content)父子通信
使用 reactive 绑定数据
使用 ref ( torefs ) 绑定数据
使用 getCurrentInstance 获取当前实例化对象上下文信
watch、watchEffect 数据监听
生命周期+axios数据请求
简单的 TodoList 点餐功能
computed 对应vue2中computed,使用方法不同

生命周期
setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
onBeforeMount() : 组件挂载到节点上之前执行的函数。
onMounted() : 组件挂载完成后执行的函数。
onBeforeUpdate(): 组件更新之前执行的函数。
onUpdated(): 组件更新完成之后执行的函数。
onBeforeUnmount(): 组件卸载之前执行的函数。
onUnmounted(): 组件卸载完成后执行的函数
若组件被 keep-alive 包含,则多出下面两个钩子函数。
onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行
onDeactivated(): 比如从 A组件,切换到 B 组件,A 组件消失时执行。
VUE2VUE3
beforeCreatesetup()
createdsetup()
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
activatedonActivated
deactivatedonDeactivated
import {
    ref,
    onBeforeMount,
    onMounted,
    onBeforeUpdate,
    onUpdated,
    onBeforeUnmount,
    onUnmounted,
    onErrorCaptured,
    onRenderTracked,
    onRenderTriggered
  } from 'vue'

  export default {
    setup() {
      let test = ref('测试');
      // 挂载开始之前被调用
      onBeforeMount(() => {
        console.log('onBeforeMount组件挂载到节点上之前执行的函数', test.value)
      });

      // 实例被挂载后调用,不会保证所有的子组件也被挂载完
      onMounted(() => {
        console.log('onMounted组件挂载完成后执行的函数', test.value)
      });

      // DOM更新前
      onBeforeUpdate(() => {
        console.log('onBeforeUpdate组件更新之前执行的函数', test.value)
      });

      // DOM更新后,不会保证所有的子组件也都一起被重绘
      onUpdated(() => {
        console.log('onUpdated组件更新完成之后执行的函数', test.value)
      });

      // 卸载组件实例之前,此时实例仍然是完全正常的
      onBeforeUnmount(() => {
        console.log('onBeforeUnmount组件卸载之前执行的函数', test.value)
      });

      // 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
      onUnmounted(() => {
        console.log('onUnmounted组件卸载完成后执行的函数', test.value)
      });

      // 当捕获一个来自子孙组件的错误时被调用
      onErrorCaptured(() => {
        console.log('onErrorCaptured当捕获一个来自子孙组件的错误时被调用', test.value)
      });

      // onRenderTracked和onRenderTriggered是用来调试的,这两个事件都带有一个DebuggerEvent,它使我们能够知道是什么导致了Vue实例中的重新渲染。。
      onRenderTracked(() => {
        console.log('onRenderTracked跟踪虚拟 DOM 重新渲染时调用', test.value)
      });
      onRenderTriggered(() => {
        console.log('onRenderTriggered当虚拟 DOM 重新渲染', test.value)
      });

      return {test};
    }
  }

#demo使用各依赖版本

{
  "name": "vue3-typescript",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build"
  },
  "dependencies": {
    "axios": "^0.21.1",
    "core-js": "^3.6.5",
    "element-plus": "^1.2.0-beta.6",
    "vue": "^3.0.0",
    "vue-router": "^4.0.0-0",
    "vuex": "^4.0.0-0"
  },
  "devDependencies": {
    "@types/node": "^17.0.0",
    "@types/webpack-env": "^1.16.3",
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-plugin-typescript": "~4.5.0",
    "@vue/cli-plugin-vuex": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0",
    "image-webpack-loader": "^7.0.1",
    "less": "^3.0.4",
    "less-loader": "^5.0.0",
    "typescript": "~4.1.5",
    "vuex-module-decorators": "^1.0.1"
  }
}

#vue3+typescript+element-plus

PS:搭建项目就不赘述了,网上好多很好的文章。
大佬文章: vue3项目搭建传送门
typescript的特性就是类型检测以及interface接口,既然决定使用typescript那么就要用,尽量不用any;
可以定义一个全局的interface接口,按需引入就可以。

//在src目录下建一个types目录创建一个store.ts
export interface TestObject {
  name: string,
  value: number
}
//在需要使用interface 接口.vue文件或者.ts文件引入
//以.vue文件举例
import { defineComponent, reactive} from 'vue';
import { TestObject } from '@/types/store';//引入类型

interface dataType {//定义setup reactive中的数据类型
  testData: Array<TestObject>,//设置数组<对象>类型
}

export default defineComponent({
	setup(){
		const data:dataType = reactive({
			 testData: [
       			 { name: '测试', value: 0 }
      		 ]
		})
	}
}

以上是在vue3中typescript基本使用


element-plus是一个支持vue3.x的UI框架
element-plus官网

//全局引入与按需引入element-plus 
//main.ts
import { createApp } from 'vue'
import App from './App.vue'
...
import ElementPlus from 'element-plus';
//import { ElButton } from 'element-plus';   //按需导入
import 'element-plus/dist/index.css'
//element-plus默认是英文,需要自己配置中文
import locale from 'element-plus/es/locale/lang/zh-cn'
const app = createApp(App);
//注册组件
//app.component('ElButton',ElButton)
...
app.use(ElementPlus, { size: 'small', zIndex: 3000, locale });

#vue3+typescript router

vue2与vue3中router还是有点区别的,尤其是加上typescript;
在main.ts中引入与注册我就不过多赘述了,主要说一下router的基础配置吧。

//在router>index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

const routes: Array<RouteRecordRaw> = [//RouteRecordRaw 当前记录的嵌套路由。
  {
    path: '/',
    name: 'Homeg',
    component: () => import(/* webpackChunkName: "Home" */ '../views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {path:'/test',name:'test',component: () => import(/* webpackChunkName: "test" */ '../views/test.vue')}
];
// Vue-router新版本中,需要使用createRouter来创建路由
// createWebHashHistory hash模式 createWebHistory History模式
const router = createRouter({
  history: createWebHistory(),
  routes
});


router.afterEach((value) => {
	//路由监听
  	console.log(value);
});

export default router
<!-- 使用路由 -->
<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link> |
  </div>
  <router-view/>
</template>

#vue3+typescript 组件传值

基础用法:

<!-- 父组件 -->
<template>
  <el-row>
    <el-col>
      <el-card>
        <el-row>
          <h2><b>父组件</b></h2>
          <el-col> <b>父组件msg1值:</b>{{ msg1 }} </el-col>
          <el-col> <b>父组件msg2值:</b>{{ msg2 }} </el-col>
        </el-row>
      </el-card>
    </el-col>
    <el-col>
      <el-divider></el-divider>
    </el-col>
    <el-col>
      <el-card>
        <!-- :msg1="msg1" @setMsg1="getMsg1" 使用基础的使用方式与vue2.x基本一致 -->
        <!-- 但是在vue3.x中移除了.sync语法糖 使用v-model替代 -->
        <Childrens :msg1="msg1" v-model:msg2="msg2" @setMsg1="getMsg1"></Childrens>
      </el-card>
    </el-col>
  </el-row>
</template>

<script lang='ts'>
import { defineComponent, reactive, toRefs } from "vue";
import Childrens from "./components/children.vue";
interface DataPType {
  msg1: string;
  msg2: string;
}
export default defineComponent({
  name: "",
  components: { Childrens },
  setup() {
    const data: DataPType = reactive({
      msg1: "父组件msg1",
      msg2: "父组件msg2",
    });
    const getMsg1 = (val:string) =>{
        data.msg1 = val
    };
    return {
      ...toRefs(data),
      getMsg1
    };
  },
});
</script>

关于vue2.x与vue3.x中语法变化,比如 .sync等语法糖的移除请看vue3语法官网

<!-- 子组件 -->
<template>
  <el-row>
    <h2><b>子组件</b></h2>
    <el-col> <b>子组件使用msg1值:</b>{{ msg1 }} </el-col>
    <el-col :span="24"> <b>子组件使用msg2值:</b>{{ msg2 }} </el-col>
    <el-col>
      <el-divider></el-divider>
    </el-col>
    <el-col>
      <el-button type="primary" @click="getPropData">获取父组件传过来的值</el-button>
      <el-button type="primary" @click="setMsg1">修改父组件msg1值</el-button>
      <el-button type="primary" @click="setMsg2">修改父组件msg2值</el-button>
    </el-col>
  </el-row>
</template>

<script lang='ts'>
import { defineComponent, reactive, toRefs } from "vue";

export default defineComponent({
  name: "Childrens",
  props: {
    msg1: String,
    msg2: { type: String, default: "" },
  },
  //setup(props)第一个参数props 以用于获取父组件传过来的值
  //setup(props,rex)第二个参数rex上下文对象(emit,slots,attrs)可以用 {emit} 或者直接 rex.emit
  //{emit} 使用 emit("XXX",需要修改的值)
  //rex 使用 rex.emit("XXX",需要修改的值)
  setup(prop, { emit }) {
    const getPropData = () => {
      console.log(prop);
      alert(prop.msg1);
      alert(prop.msg2);
    };
    const setMsg1 = () => {
      //基础用法 与 vue2.x 改动不大
      emit("setMsg1", "这是子组件修改的值(msg1)");
    };
    const setMsg2 = () => {
      emit("update:msg2", "这是子组件修改的值(msg2)");
      //同等于 vue2.x中this.$emit("update:msg2", "这是子组件修改的值(msg2)")
      //父组件在接收时不是在使用 msg2.sync="msg2" 而使用v-model:msg2="msg2"
    };
    return {
      getPropData,
      setMsg1,
      setMsg2,
    };
  },
});
</script>

PS:关于toRefs的使用与介绍(大佬文章: 传送门);说一下比较实用的吧。

<template>
  <el-row>
    <!-- <el-col>注1: {{ data.msg }} </el-col> -->
    <el-col>注2: {{ msg }} </el-col>
  </el-row>
</template>

<script lang='ts'>
import { defineComponent, reactive, toRefs } from "vue";
interface DataPType {
  msg: string;
}
export default defineComponent({
  setup() {
    const data: DataPType = reactive({
      msg: "1",
    });
    return {
      //data,//直接把 data 抛出去在 template中使用需要 data.msg (注1)
      ...toRefs(data),//使用了 toRefs(data) 的话,在template直接可以访问 msg (注2)
    };
  },
});
</script>

#vue3+typescript+watch

上代码

<template>
  <div>
    <h1>watch、watchEffect 数据监听</h1>
    <p>msg: {{msg}}</p>
    <p>msg2: {{msg2}}</p>
    <p>
      <button @click="changeMsg">changeMsg</button>
    </p>
    <p>
      <button @click="changeMsg2">changeMsg2</button>
    </p>
  </div>
</template>
<script>
import {defineComponent, reactive, watchEffect, watch, toRefs} from 'vue';

  export default defineComponent({
    setup() {
      const state: any = reactive({// 使用响应式函数reactive构建proxy响应式对象state
        msg: 'value1',
        msg2: 'value2',
        changeMsg2: () => {
          state.msg2 = '改变value2'
        }
      });

      const changeMsg = () => {// 在外边定义methods
        state.msg = '改变value1';
      };
      // watch监听只能是 getter/effect 函数、ref、reactive对象或数组
      // 简单监听
      watch(state, () => {
        // 只要state中的值有变化,就会打印
        console.log('观察整个state中的属性变化'+ '%c' + '《' +  state.msg + '》','color:red');
      });
      // 监听指定的信息
      watch(
          () => state.msg, (newVal, oldVal) => {
            console.log('01-msg的新值是: ' + '%c' + '《' +  newVal + '》','color:red');
            console.log('01-msg的旧值是: ' + '%c' + '《' +  oldVal + '》','color:red');
          }
      );
      // 监听多个属性(数组形式)
      watch([() => state.msg, () => state.msg2], (newVal, oldVal) => {
        console.log('02-msg的新值是: ' + '%c' + '《' +  JSON.stringify(newVal) + '》','color:yellow');
        console.log('02-msg的旧值是: ' + '%c' + '《' +  JSON.stringify(oldVal) + '》','color:yellow');
      });
      // 不需要指定监听的属性
      watchEffect(_ => {
        // 程序运行时,初始化就会执行一次,完成监听准备工作
        // 有点像computed计算属性,用到的数据发生改变才会执行
        console.log('03-watchEffect监听 state 中数据变化'+'%c' + '《' + state.msg + '》','color:lime')
      });


      return {
        ...toRefs(state),
        changeMsg,
      }
    }

  })
</script>

#vue3+typescript 使用axios模块化

axios中文文档

说一下我根据以前vue2.x常用的axios的配置改为vue3.x+typescript的配置

//先说一下啊目录结构
src
--http
---index.ts
---request.ts
---api
----queryAip.ts
//http>index.ts

/** 请求封装 **/

import axios from 'axios';
import {Payload} from '@/types/store'
//export interface Payload {//可以规定一下后端返回的数据类型
//  code: string,
//  msg?: string,
//  data?: any
//}

//封装post(get、delete...与下面没有太大差异)
export const POST = (url: string, params: object) => {
    return new Promise<Payload>((resolve, reject) => {
        axios.post(url, params).then(res => {
            resolve(res.data)
        }).catch(err => {
            reject(err)
        })
    })
};
//http>request.ts

/** 请求拦截 **/

import axios, {AxiosInstance, AxiosRequestConfig, AxiosResponse} from 'axios';

//axios 拦截


export class Request {
    // 初始化拦截器
    public static initInterceptors() {
        axios.defaults.headers.post['Content-Type'] = 'application/json';
        /**
         * 请求拦截器
         * 每次请求前,如果存在token则在请求头中携带token
         */
        axios.interceptors.request.use(
            (config: AxiosRequestConfig) => {
                config.headers.token = '';
                return config;
            },
            (error: any) => {
                console.log(error);
            },
        );
        // 响应拦截器
        axios.interceptors.response.use(
            // 请求成功
            (response: AxiosResponse) => {
            	//...
            },
            // 请求失败
            (error: any) => {
                const {response} = error;
                if (response) {
                   //...
                } else {
                    // 处理断网的情况
                    // eg:请求超时或断网时,更新state的network状态
                    // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
                    // 关于断网组件中的刷新重新获取数据,会在断网组件中说明
                    console.log('网络连接异常,请稍后再试!');
                }
            });
    }
    /**
     * http握手错误
     * @param res 响应回调,根据不同响应进行不同操作
     */
    private static errorHandle(res: any) {
        // 状态码判断
        switch (res.status) {
            case 401:
                break;
            case 403:
                break;
            case 404:
                console.log('请求的资源不存在');
                break;
            default:
                console.log('连接错误');
        }
    }
}
//http>api>queryApi.ts

/** 请求接口封装 **/

import { POST } from '@/http/index';
import {postType} from '@/types/store';
//export interface postType{
//  limit: number,
//  start: number
//}

//封装接口
const GetList = (obj: postType) => POST('/list',obj);

export {GetList}
//全局引入与注册
//main.ts
import { createApp } from 'vue'
import App from './App.vue'

import { Request } from '@/http/request'
const app = createApp(App);
...
app.use(Request.initInterceptors);//初始化拦截器
//使用请求数据
<script lang='ts'>
import { defineComponent, reactive, onMounted } from 'vue';
import { GetList } from '@/http/api/textApi'
import { InstanceType } from '@/types/store';

//export interface InstanceType {//定义返回数据类型
//  resultList: {}[],
//  start: number,
//  limit: number,
//}


interface dataType {
  tableData: InstanceType[]
}

export default defineComponent({
  setup() {
    let data: dataType = reactive({
      tableData: []
    });
   
    const getAxiosData = (start: number = 1) => {
      GetList ({ start, limit: 10 }).then(res => {
        if (res.code === '000000') {
          data.tableData = res.data.resultList;
        }
      });
    };
    
    onMounted(() => {
      getAxiosData()
    });

    return {
      getAxiosData,//return出去可以在页面绑定
    }
  },

});
</script>

#vue3+typescript vuex

在vue2.x中使用vuex的结构基本同步移动vue3.x中

//看一下目录结构
src
--store
---index.ts
---actions.ts
---mutations.ts

上代码

//store > index.ts
import {createStore} from 'vuex'//引入vuex 在vuex4版本中需要使用 createStore来构建
import actions from './actions';//引入actions
import mutations from './mutations';//引入mutations

interface StoreType {//定义 state 中数据类型
    vuexValue:string,
}

export default createStore({
    state:<StoreType> {
        vuexValue:'初始值'
    },
    mutations,
    actions,
})
//store > actions.ts
import {Commit} from 'vuex'//引入 Commit 类型

const Actions = {
  setVuexValue(context: { commit: Commit }, value: string) {
    context.commit('SET_VUEX_VALUE', value)
  }
};

export default Actions
//store > mutations.ts
const Mutations = {
    SET_VUEX_VALUE(state: any, data: string){
        state.vuexValue = data
    },
};

export default Mutations

以上是vuex基本使用,其他的也不多少说了;下面介绍一下在.vue里面使用;

<template>
  <el-row>
    <el-col>VUEX-vuexValue值: {{ vuexValue }}</el-col>
    <el-col>
      <el-button type="primary" @click="setVuexValue('VUEX数据修改')">修改VUEX数据</el-button>
    </el-col>
  </el-row>
</template>

<script lang='ts'>
import { defineComponent, reactive, toRefs, computed } from "vue";
// import { useStore } from "vuex"; // 1 => 使用vuex方式
import store from '@/store'// 2 => 使用vuex方式

interface DataPType {}
export default defineComponent({
  name: "vuex4",
  setup() {
    const data: DataPType = reactive({});
    // const store = useStore();  //1 => 使用vuex方式 //vuex实例化

    const setVuexValue = (value: string) => store.dispatch("setVuexValue", value); // 1,2 => 使用vuex方式
    const vuexValue = computed(() => store.state.vuexValue); // 1,2 => 使用vuex方式
    return {
      ...toRefs(data),
      setVuexValue,
      vuexValue,
    };
  },
});
</script>

#vue3+typescript vuex进阶

说一下vuex配合vuex-module-decorators的用法吧;
vuex-module-decorators的安装看一下官方的文档吧;传送门

//看一下目录结构
src
--store
---index.ts
---module
----test.ts

上代码!

//index.ts
import { createStore } from 'vuex'
import { config } from 'vuex-module-decorators' //引入 vuex-module 中的 config
config.rawError = true;//如果不设置  module层会拦截报错 会给出module层报错信息 设置为true 报错会正常
export default createStore({})
//test.ts
import { Module, VuexModule, Mutation, Action, getModule } from 'vuex-module-decorators'
import store from '../index'

// dynamic: true: 动态创建动态模块,即在 index.ts export default createStore({})里面不用注册的.空着就行,
// store,当前模块注册到store上.也可以写在getModule上,即getModule(PassengerStore,store)
// namespaced: true, 
// name: 'passenger' 命名空间

@Module({ dynamic: true, store, name: 'TEST' })

export default class test extends VuexModule {
    // 定义变量需要 使用 public 不然外面访问不到
    
    /**
     * public 默认共有属性
     * private 私有属性
     * protected 私有属性,派生类中仍可访问
     * readonly 只读属性
     * static 静态属性
     * abstract 抽象类或方法
     * **/

    public vuexValue: string = '';//可直接使用 computed(() => TestModule.vuexValue)

    get GetVuexValue(): string { //也可使用 computed(() => TestModule.GetVuexValue)
        return this.vuexValue
    }

    // @Mutation // 注意:每一个 Mutation、Action... 都需要在前面声明 @XXX
    // XXX(data: string) {
    //     this.XXX = data;
    // };
    // 在 class 中 this 指向的是当前 class
    @Mutation
    SET_VUEX_VALUE(data: string) {
        this.vuexValue = data;
    };

    @Action
    public setVuexValue(value: string) {
        this.SET_VUEX_VALUE(value)
    };
}

export const TestModule = getModule(test)

在 .vue 中使用

<template>
  <el-row>
    <el-col>vuex数据: {{ testValue }}</el-col>
    <el-col>
      <el-button type="primary" @click="setVuexValue('改变vuex值')"
        >改变vuex值
      </el-button>
    </el-col>
  </el-row>
</template>

<script lang='ts'>
import { defineComponent, reactive, toRefs, computed } from "vue";
import { TestModule } from "@/store/module/test"; //引入 vuex module

interface DataPType {
  testValue: string;
}
export default defineComponent({
  name: "",
  setup() {
    const data: DataPType = reactive({
      testValue: computed(() => TestModule.vuexValue), //也可以直接调用
      setVuexValue: (value: string) => TestModule.setVuexValue(value), //也可以直接调用,不过不建议这样
    });
    // 把 TestModule.setVuexValue 赋值到 setVuexValue 中, 也可以直接调用 TestModule.setVuexValue()
    // const setVuexValue = (value: string) => TestModule.setVuexValue(value);//第一种使用setVuexValue
    // const vuexValue = computed(() => TestModule.vuexValue);//第一种使用vuexValue
    return {
      ...toRefs(data),
      // setVuexValue,//第一种使用setVuexValue
      // vuexValue,//第一种使用vuexValue
    };
  },
});
</script>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值